feat: relative mouse on the HID interface, and the verifier to prove it
The board now enumerates as a boot keyboard plus a relative mouse on the same HID interface. The pointer is captured, never grabbed: Pointer Lock in the page, so Esc (the browser's own, never forwarded) always frees it, and every frame restates the whole button byte so a frame lost in the WiFi cannot leave a button held. Losing the lock, the tab or the link runs the same release funnel as a stuck key, and boot protocol or `mouse off` refuses mouse frames with E11 rather than sending bytes a boot interface would misparse as keys. src/mouse.h owns the report, src/output.h the queues and the funnel; the page, relayctl.py and watch_target.py carry the button bits and the evdev decoding. The verifier's own tables are pinned now — REL_WHEEL is 8, not 2, and the first version guessed from the report's field order, so a working wheel looked dead while the summary said no scroll happened. LLM-Generated: true
This commit is contained in:
@@ -84,6 +84,22 @@ constexpr uint16_t kTextPeriodMsDefault = 12; // ms per character when asked fo
|
||||
constexpr uint16_t kTextPeriodMaxMs = 250; // slowest pace a controller may ask for
|
||||
constexpr uint16_t kTextHoldMaxMs = 6; // how long one character is held down
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mouse
|
||||
// ---------------------------------------------------------------------------
|
||||
// The controller captures the pointer with the browser's own Pointer Lock and
|
||||
// forwards it as relative HID mouse reports. One M= frame is one report: the button
|
||||
// byte is restated every frame, and a movement delta is self-contained, so a frame
|
||||
// that never arrives moves the cursor a little less and strands nothing. See mouse.h.
|
||||
//
|
||||
// The deltas are capped at one report's worth (a signed byte) because one frame is
|
||||
// one command: splitting a large movement into several frames is the controller's
|
||||
// job, exactly like splitting a long paste. The board does not split -- it would turn
|
||||
// one command into a queue depth nobody asked for.
|
||||
constexpr int8_t kMouseDeltaMax = 127; // one signed byte, the report's range
|
||||
constexpr uint8_t kMouseButtonMask = 0x1F; // 5 buttons: all the report can carry
|
||||
constexpr size_t kMouseReportQueueDepth = 32; // movement bursts, same fail-safe
|
||||
|
||||
// Safety: a bridge that has heard nothing from its controller for this long is
|
||||
// a bridge that should not be typing. Arming never survives it (laptop slept,
|
||||
// cable unplugged, browser killed).
|
||||
@@ -95,6 +111,11 @@ constexpr uint32_t kStatusIntervalMs = 1000;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Control-server buffers
|
||||
// ---------------------------------------------------------------------------
|
||||
// The status JSON, sized for every field in StatusSnapshot plus headroom. Sized in
|
||||
// one place because three call sites build it and a truncated status lies about state,
|
||||
// which is worse than no status.
|
||||
constexpr size_t kStatusJsonCap = 384;
|
||||
|
||||
constexpr size_t kHttpHeaderCap = 1536; // reject a request line bigger than this
|
||||
constexpr size_t kWsMessageCap = 256; // one controller frame is a command, not a payload
|
||||
constexpr uint32_t kAuthTimeoutMs = 5000; // a new socket must authenticate this fast
|
||||
|
||||
Reference in New Issue
Block a user