$timeout) { // Timeout - store partial sequence for retry if (strlen($prefix . $buffer) > 0) { $this->eventBuffer->storePartialSequence($prefix . $buffer); } return null; } usleep(1000); // 1ms continue; } $buffer .= $char; // Mouse event ends with 'M' (press) or 'm' (release) if ($char === 'M' || $char === 'm') { break; } // Safety: limit buffer size if (strlen($buffer) > self::MAX_BUFFER_SIZE) { return null; } } // Parse format: b;x;y where b is button code, x and y are coordinates $data = substr($buffer, 0, -1); $parts = explode(';', $data); if (count($parts) < 3) { // Invalid mouse event format return null; } $buttonCode = (int) $parts[0]; $x = (int) $parts[1]; $y = (int) $parts[2]; // Validate coordinates if ($x < 1 || $y < 1 || $x > self::MAX_X || $y > self::MAX_Y) { // Invalid coordinates, likely corrupted return null; } // Decode button and modifiers $button = $buttonCode & 0x03; $shift = ($buttonCode & 0x04) !== 0; $alt = ($buttonCode & 0x08) !== 0; $ctrl = ($buttonCode & 0x10) !== 0; // Handle scroll events (button codes 64 and 65) if ($buttonCode >= 64 && $buttonCode <= 65) { $button = $buttonCode; } elseif (($buttonCode & 0x20) !== 0) { // Mouse move (button code 32 or bit 5 set) $button = $buttonCode; } $pressed = $buffer[-1] === 'M'; return new MouseEvent( x: $x, y: $y, button: $button, pressed: $pressed, shift: $shift, ctrl: $ctrl, alt: $alt ); } /** * Check if sequence is a mouse event prefix */ public function isMouseEventPrefix(string $sequence): bool { return str_starts_with($sequence, "\033[<"); } }