Skip to content

Manual verification: macOS input injection (key, click, scroll) on a real session #141

Description

@jonocodes

What to verify

The macOS injection rows in docs/PLATFORM-PARITY.md are 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:

capability evidence today gap
pointer / drag machine-verified (cursor position read back after pad frames)
scroll (jogstrip) jog frames sent, no assertion did anything actually scroll?
click (left / right) never fired at a real target does a tap select / does two-finger tap open a context menu?
key injection (printable, combos, special) MacKeySink._check_accessibility's osascript probe exits 0 a passing probe is not a delivered keystroke

The probe sends keystroke "" — an empty string. It proves the System Events grant exists; it proves nothing about _build_keystroke_script output 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 — MacKeySink now warns at startup when AXIsProcessTrusted() is false, so check the daemon log first. Note the grant follows the responsible process: the terminal or launchd agent that started the daemon, not python.

just setup && just build-client
just dev-daemon-lan        # then open the client on a phone or a second browser

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.

  • Scroll lands. Focus a long web page or document; drag the chrome jogstrip. The content scrolls in the drag direction, and release-with-velocity produces momentum decay rather than an abrupt stop.
  • Left click lands. Move the cursor onto a button/link with the trackpad widget, quick-tap. The control activates (not just a focus ring).
  • Right click lands. Two-finger tap on the trackpad widget over a page. A context menu opens.
  • Drag lock lands. Tap-and-a-half, then drag: a text selection or window move actually follows the finger, and releases on lift (this is the LeftMouseDragged path that motivated Quartz over cliclick).
  • Printable key lands. A key: "a" button types a into a focused text field.
  • Combo lands. A key: "super+t" button opens a new tab in the focused browser (Command, not Control — _MOD_CLAUSE maps super/meta → command).
  • Bracket keys land. super+[ / super+] navigate back / forward in the browser — these go through key code rather than keystroke precisely because the character form is layout-dependent.
  • Special keys land. Esc, Tab, arrows, Enter, and an F-key each do the expected thing in a focused app (the _MAC_KEY_CODE map is documented as partial — record which ones are missing rather than only pass/fail).
  • Manual control mode round-trips. In manual control, typing on the phone's IME inserts the literal characters into the focused desktop app, and the strip keys (Esc/Tab/arrows) work.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    human-verification-requiredCode is complete; a human must verify on real hardware / a live session before closinginput-injectionSynthetic input via uinput or platform equivalent

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions