Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
120 lines
3.2 KiB
PHP
120 lines
3.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Console\Components\Parsers;
|
|
|
|
use App\Framework\Console\Components\EventBuffer;
|
|
use App\Framework\Console\Components\MouseEvent;
|
|
|
|
/**
|
|
* Isolated mouse event parser.
|
|
* Handles SGR mouse events (\e[<b;x;yM or \e[<b;x;ym)
|
|
*/
|
|
final readonly class MouseEventParser
|
|
{
|
|
private const int MAX_BUFFER_SIZE = 20;
|
|
private const int TIMEOUT_MS = 10;
|
|
private const int MAX_X = 1000;
|
|
private const int MAX_Y = 1000;
|
|
|
|
public function __construct(
|
|
private EventBuffer $eventBuffer
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Parse mouse event from sequence prefix \e[<
|
|
*/
|
|
public function parse(string $prefix): ?MouseEvent
|
|
{
|
|
$buffer = '';
|
|
$timeout = self::TIMEOUT_MS;
|
|
$startTime = microtime(true) * 1000;
|
|
|
|
// Read until we get 'M' or 'm'
|
|
while (true) {
|
|
$char = fgetc(STDIN);
|
|
if ($char === false) {
|
|
// Check timeout
|
|
$elapsed = (microtime(true) * 1000) - $startTime;
|
|
if ($elapsed > $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[<");
|
|
}
|
|
}
|
|
|