Spike result: Keyboard wedge (task 1.2)¶
Status: macOS and Windows done; Linux not run (no machine available); no real scanner available · Date: 2026-09-15 · Run by: Ahmed Alrifai · Kit: spikes/keyboard-wedge/
Question¶
- Do injected keystrokes (Windows
SendInput, macOSCGEventPost, X11 XTest) give the samekey,codeand timing as a real USB scanner, in a browser and in a native text field? - With an Arabic keyboard layout active, are letter-bearing scans garbled the same way as with a real scanner, and does
--unicodetype the exact characters? - Which app does macOS attribute the Accessibility permission to (Terminal, other terminals, uv's tool Python)?
Environment¶
| Windows | macOS | Linux | |
|---|---|---|---|
| OS version / desktop session | Windows 11 Pro 10.0.26200 | macOS 26.5 (25F71) | |
| Browser and version | Edge 152 (Chromium; the runbook said Chrome, the logs' user agent says Edg/152) |
Chrome 153 | |
| Native text field app | Notepad | TextEdit | |
| Keyboard layouts installed | en-US (0x04090409) and Arabic (Libya) with the Arabic (101) keyboard (0x04011001); Arabic (Saudi Arabia) 0x04010401 added during step 1.9 |
US-compatible default (48/48 key codes match) + Arabic – PC | |
| Scanner model (if used) | none available | none available | |
| Python / uv version | CPython 3.13 via uv 0.7.13 | Homebrew Python 3.13.7 / uv 0.8.18 |
Observations¶
Windows¶
Logs: kb-*.json and kb-*.txt from the Windows results zip. Every run had sizeof(INPUT) = 40; repeat and isComposing were false on every event.
- Dry run:
sizeof(INPUT)40; the US layout (0x04090409) was already installed, so the script neither loaded nor unloaded it; language switcher changed? Not applicable: English (US) was already installed, so the script had nothing to load (confirmed in the recheck). Key events: VK0x35 0x39 0x30 0x31 …with scan codes0x06 0x0a 0x0b 0x02 …, Enter VK0x0dscan0x1c. - Edge,
--keys vk-scan(kb-02): assembled5901234123457⏎, field5901234123457;codeof digitsDigit0toDigit9(keyCode 48 to 57); EntercodeEnter; accepted 28 of 28 (159 ms). Keydowns arrived 10.8 to 12.5 ms apart, 149 ms in total: Windows kept the 10 ms spacing, where macOS delivered a burst. - Edge,
--keys vk(kb-03):codeempty on every keydown and keyup;keyand keyCode correct, field5901234123457. - Edge,
--keys scan(kb-04):codeDigitNandEnter, identical to vk-scan (the foreground layout was US). - Edge,
"Ab1-"(kb-05):Shift/ShiftLeftdown,A/KeyAdown and up withshiftKeytrue,ShiftLeftup, thenb/KeyB,1/Digit1,-/Minus(keyCode 189),Enter, all withshiftKeyfalse; fieldAb1-; accepted 12 of 12. Same sequence as macOS. - Edge,
--unicode "كود-42"(kb-06): fieldكود-42exactly; every keydown hadkey= the exact character,codeempty and keyCode 231 (VK_PACKET), including-,4and2; every keyup hadkeyUnidentified; Enter keptEnter/13; accepted 14 of 14. - Notepad (default,
"Ab1-"):5901234123457andAb1-, each followed by a new line. - Arabic layout (default,
"Ab1-"), Edge / Notepad (foreground layout0x04011001, Arabic (101)): - Default, Edge (
kb-08): field5901234123457in ASCII digits,keythe ASCII digit,codeDigitN. The Arabic (101) keyboard types ASCII digits on the number row, unlike macOS "Arabic – PC", which types Arabic-Indic digits. "Ab1-", Edge (kb-09): fieldِلا1-(kasra, lam-alef,1,-). Shift+KeyAgave the kasra (keyCode 65);KeyBtyped the two charactersلا, but its keydown and keyup reportedkeyUnidentified(keyCode 66);Digit1gave1. Assembled from keydownkey:ِ1-, so an app readingkeylosesلاcompletely (macOS reportedاand lost only the lam).- Notepad:
5901234123457andِلا1-, the same as the Edge field. - Notepad as administrator (
kb-11, run twice, Arabic still the foreground layout): SendInput accepted 28 of 28 both times. Recheck (re-03-admin-notepad.txt, run twice from a PowerShell confirmed not elevated, US layout): accepted 28 of 28 both times, and nothing appeared in the elevated Notepad. Windows discarded the keystrokes and still reported every one as accepted, as Microsoft documents for UIPI. - Real scanner: not tried, no scanner available.
macOS¶
- Dry run from Terminal.app: AX trusted False; CGPreflightPostEventAccess False; responsible app
/System/Applications/Utilities/Terminal.app(TERM_PROGRAMApple_Terminal, bundle idcom.apple.Terminal); layout check 48/48 US key codes match. The script printed the NOT TRUSTED message; same key codes as below. - From Warp (already granted Accessibility): AX trusted True; CGPreflightPostEventAccess True; responsible app
/Applications/Warp.app(TERM_PROGRAMWarpTerminal, bundle iddev.warp.Warp-Stable); layout check 48/48 US key codes match. Events for5901234123457+ Enter were the expected US ANSI key codes (0x17 0x19 0x1d 0x12 0x13 0x14 0x15 0x12 0x13 0x14 0x15 0x17 0x1a, then0x24), no shift. - Not trusted: message and named app matched System Settings? Yes: enabling Terminal in Accessibility and restarting Terminal made both checks True;
--promptdialog named ___ (not tried) - Attribution with only Terminal.app granted: other terminal (Warp) reports not trusted (both checks False, responsible app
/Applications/Warp.app); uvx Python from Terminal reports trusted (both True, responsible app Terminal.app). uvx picked the same Homebrew Python 3.13.7 asuv run, so the permission follows the terminal app, not the Python binary. Confirmed withuvx --managed-python(uv-managed CPython 3.13.7 in~/.local/share/uv/python/): still trusted, responsible app Terminal.app. - Chrome (default,
"Ab1-"): key/code as below; TextEdit:5901234123457andAb1-typed exactly, each followed by a new line (Enter) - Default (
5901234123457+ Enter,--delay-ms 10), logmacos-chrome-default.json: field value5901234123457, Enter delivered. Every digit hadkey= the digit andcodeDigitN(keyCode 48 to 57); Enter hadkey/codeEnter, keyCode 13;shiftKey,repeatandisComposingfalse throughout. Timing as seen by the page (performance.now()in the handler): 91 ms from the first keydown to the last keyup; the first keyup came 67 ms after its keydown, then the remaining 13 keystrokes arrived within 24 ms (0 to 5 ms apart), so Chrome received the posted 10 ms spacing in a burst. "Ab1-", logmacos-chrome-Ab1.json: field valueAb1-, Enter delivered. Sequence:Shift/ShiftLeftdown,A/KeyAdown and up withshiftKeytrue,ShiftLeftup, thenb/KeyB,1/Digit1,-/Minus(keyCode 189),Enter, all withshiftKeyfalse. Shift wraps only the capital letter, as a US keyboard does. 109 ms from Shift down to Enter up, with the same burst pattern (76 ms before the first letter, then 0 to 15 ms apart).--unicode "كود-42": Chrome key/code: field valueكود-42exactly, Enter delivered (logmacos-chrome-unicode.json). Every character's keydown hadkey= the exact character butcodeKeyA(the script posts key code 0 with the text attached); keyCode was 65 for the Arabic letters and 189, 52, 50 for-,4,2. Every keyup reportedkeya/codeKeyA, even though the script attaches the text to the keyup too. So--unicodegives the right text in the field and in keydownkey, but an app that readscode, keyCode of letters or keyupkeyseesKeyA/a. The Enter suffix kept its realEntercode. TextEdit:كود-42typed exactly, followed by a new line- Arabic input source (default,
"Ab1-"), Chrome / TextEdit (input source "Arabic – PC"; the dry run's layout check reported4/48 US key codes matchand listed each mismatch, e.g. key 0x00 givesش, 0x0b givesلا, 0x12 gives١, so the check can tell a non-US input source apart): - Default, Chrome (log
macos-chrome-arabic-default.json): field value٥٩٠١٢٣٤١٢٣٤٥٧, Arabic-Indic digits (U+0660 to U+0669) instead of ASCII; Enter delivered.codestayedDigitNand keyCode 48 to 57, onlykeychanged to the Arabic-Indic digit. A POS that looks up the field value gets no match unless it converts the digits. - Default, TextEdit:
٥٩٠١٢٣٤١٢٣٤٥٧, Arabic-Indic digits. The first attempt showed the first٥doubled (14 digits); a re-run in a new document gave the correct 13, so the doubling did not reproduce. "Ab1-", Chrome (logmacos-chrome-arabic-Ab1.json): field valueِلا١-(kasra U+0650, lam-alefلا,١,-). Shift+KeyAgave the kasra with keyCode 65;KeyBtyped two charactersلاbut its keydown reportedkeyاonly and keyCode 229, keyup keyCode 66;Digit1gave١;Minusstayed-. The string assembled from keydownkey(ِا١-) differs from the field value, so an app readingkeyloses the lam."Ab1-", TextEdit:ِلا١-, same as the Chrome field.- The script sends US key codes regardless of the input source, so this is the garbling a real US-layout scanner should also produce; not yet compared with a real scanner.
- Real scanner: not tried, no scanner available
Linux (X11)¶
XDG_SESSION_TYPE___; XTEST version ___; libXtst package ___- Chrome (default,
"Ab1-"): key/code ___; gedit ___ us,aralayout with Arabic active,"Ab1-": ___ (the script uses the current keymap; a real scanner sends the physical US key)- Wayland with
--try-xwayland(optional): ___ - Real scanner:
code___; delta ___ ms
Decision¶
- Windows key events: [x] vk-scan [ ] vk [ ] scan. Reason: without a scan code Chromium leaves
codeempty (step 3); scan alone matched vk-scan in the browser, and vk-scan also gives applications that read virtual-key codes the US key. - macOS permission check: [x] Quartz
CGPreflightPostEventAccessis enough [ ] also needsAXIsProcessTrustedWithOptions(adds pyobjc-framework-ApplicationServices). Reason: the two checks agreed in every run (Warp granted and not granted, Terminal before and after the grant, uvx with Homebrew and uv-managed Python). - X11 key lookup: [ ]
XKeysymToKeycodeis acceptable [ ] use a fixed US keycode table (not decided: Linux not run) - Default
inter_key_delay_msof 10: [x] fine [ ] change to ___. Reason: no keystroke was lost on either OS; Windows delivered them about 11 ms apart, macOS in a burst.
Impact on specs and tasks¶
- barcode-scanner spec ("US-layout key codes by default", "macOS Accessibility permission", "Linux keyboard mode requires X11", "Windows privilege limitation"): "US-layout key codes by default" holds on Windows and macOS: with an Arabic layout the letters change, and digits change or not depending on the layout (Arabic (101) keeps ASCII digits, macOS Arabic – PC does not). "Unicode mode" types the exact text, but the key events carry no physical key (
codeempty and keyCode 231 on Windows,KeyA/aon macOS), which the spec should say. "macOS Accessibility permission": the permission belongs to the terminal app that starts emupos, whatever Python runs it. "Windows privilege limitation": SendInput reported 28 of 28 accepted while nothing reached the elevated Notepad, so the warning for fewer accepted keystrokes does not catch UIPI; the spec now says Windows reports such keystrokes as accepted. "Linux keyboard mode requires X11": not tested. - design.md D10 and the Open Question on
KeyboardEvent.code: Windows row becomesSendInputwith virtual-key and scan codes. The question is answered for Chromium browsers (Edge 152 on Windows, Chrome 153 on macOS):code,keyand keyCode match a US keyboard. Electron, WPF and Java apps were not tested, and nothing was compared with a real scanner. - pyproject.toml darwin dependencies: no change;
transports/keyboard/macos.pyalready checks onlyCGPreflightPostEventAccess. - tasks 9.2 to 9.5: 9.2 sends both the US virtual-key code and its scan code, uses
KEYEVENTF_UNICODEfor the Unicode mode, and cannot rely on the accepted count to detect UIPI. 9.3 matches this spike. 9.4 is implemented but its spike checklist was not run. 9.5 unchanged.