Repository navigation
Manual verification: macOS input injection (key, click, scroll) on a real session #141
Description
Activity
- addedinput-injectionSynthetic input via uinput or platform equivalentSynthetic input via uinput or platform equivalenthuman-verification-requiredCode is complete; a human must verify on real hardware / a live session before closingCode is complete; a human must verify on real hardware / a live session before closing
on Aug 7, 2026 - added a commit that references this issue
on Aug 7, 2026 Verification run — 2026-08-06, macOS 15.6.1, Apple Silicon
Driven through the running daemon over its WebSocket (
pad/pad_tap/pad_drag/jog/key/typeframes), with results read back out of a local HTML page in Chrome that recordsmousemove/mousedown/click/contextmenu/keydown/scrollTop. That makes most of this checklist machine-checked rather than eyeballed — the cheap macOS analogue of the #132 echo receiver.Two notes on method:
- Injection had to come from the daemon's process tree, not a fresh shell. TCC attributes Accessibility to the responsible process, so events posted from anywhere else are silently dropped (that's what
AXIsProcessTrusted()now warns about at startup). - The driver refuses to fire unless
/diagreports the target app focused, and raises it throughraise_windowfirst — a mis-aimed click can't land in someone else's window.
Results
criterion result evidence Scroll lands ✅ scrollTopon the element under the cursor moved; page itself didn't scroll, so targeting is rightScroll — momentum ❌ daemon decay runs ( active_momenta1 → 0 over ~0.4s) but nothing reaches the app → #143Left click lands ✅ clickat client (755, 138) — exactly the injected screen point minus the window offsetRight click lands ✅ contextmenuat client (725, 230), same coordinate matchDrag lock lands ✅ 1 × mousedown, 5 ×mousemovewithbuttons === 1stepping exactly +50/+20, 1 ×mouseupPrintable key lands ✅ type: "hello"→ textarea valuehello, fivekeydownsCombo lands ✅ super+topened a tab (visible in the CDP page list),super+wclosed it;shift+aappendedABracket keys land ✅ super+[went back (URL lost?page=second),super+]went forward (regained it)Special keys land ✅ Escape,ArrowUp/Down/Left/Right,Enter,F9,Taball delivered with correctcodeManual control round-trip ⬜ not covered — the wire-level equivalents ( type+key) pass above, but typing on the phone's own IME still needs a human with the phoneBug found: scroll accumulator direction asymmetry (fixed)
MacScrollSinkaccumulated hi-res units and used floor division to convert to detents.-4 // 120 == -1, so the first downward tick of any gesture fired a whole detent and left the remainder at +116 — which then swallowed the next 120 units. Upward scrolling needed a full 120 units before anything happened. Symmetric input, asymmetric output:10 x -4 (40 units down) -> 1 detent emitted, remainder +80 10 x +4 (40 units up) -> 0 detents emitted, remainder +40Fixed by truncating toward zero instead (
int(remainder / DETENT)), with tests pinning symmetry and remainder carry-over. Re-verified live afterwards: 40 units in either direction now moves nothing, 120 units moves exactly one detent, both ways.That fix does not make momentum visible — the travel budget is the separate problem in #143.
Doc updated
docs/PLATFORM-PARITY.md's verification table now records scroll / click / key as machine-verified with this date, and momentum as a known gap.- Injection had to come from the daemon's process tree, not a fresh shell. TCC attributes Accessibility to the responsible process, so events posted from anywhere else are silently dropped (that's what
What to verify
The macOS injection rows in
docs/PLATFORM-PARITY.mdare implemented, typechecked, and unit-tested — but nobody has watched an injected event land in an application. The 2026-08-06 hardware pass (macOS 15.6.1, Apple Silicon) machine-verified focus, enumeration,raise_window, and pointer motion by driving a live daemon over its own WebSocket API, and stopped there:padframes)jogframes sent, no assertionMacKeySink._check_accessibility's osascript probe exits 0The probe sends
keystroke ""— an empty string. It proves the System Events grant exists; it proves nothing about_build_keystroke_scriptoutput arriving in the focused app, which is the part with a hand-written HID-code map (_MAC_KEY_CODE) and a modifier-clause builder.This is a manual verification pass — no code changes expected unless a real bug surfaces, in which case file a follow-up ticket.
Repro environment
Any Mac running the daemon from a terminal that holds the Accessibility grant (System Settings → Privacy & Security → Accessibility). Without it, Quartz events are silently dropped and the whole exercise measures nothing —
MacKeySinknow warns at startup whenAXIsProcessTrusted()is false, so check the daemon log first. Note the grant follows the responsible process: the terminal or launchd agent that started the daemon, notpython.Acceptance criteria
Record pass / fail plus a one-line observation for each. A single failing scenario is a follow-up ticket, not a blocker on closing this one.
LeftMouseDraggedpath that motivated Quartz over cliclick).key: "a"button typesainto a focused text field.key: "super+t"button opens a new tab in the focused browser (Command, not Control —_MOD_CLAUSEmapssuper/meta→ command).super+[/super+]navigate back / forward in the browser — these go throughkey coderather thankeystrokeprecisely because the character form is layout-dependent._MAC_KEY_CODEmap is documented as partial — record which ones are missing rather than only pass/fail).Follow-up
Update the evidence table in
docs/PLATFORM-PARITY.md("Verification status") with the results and the date — that table is the thing this ticket exists to make honest. Related: #132 would automate this class of check via an echo-receiver app; this ticket is the cheap human version in the meantime.