pyautogui's Quartz-based keystroke path often fails on newer macOS
because the Python CLI binary doesn't auto-surface in System Settings
> Accessibility. User reported the daemon detected taps fine but
keystrokes never landed in any window.
Switch to AppleScript / System Events on macOS. Permission attaches
to whatever terminal/app launched the Python process (Terminal.app,
iTerm, etc.) — a familiar named app the user can grant Accessibility
to in one click. Combined keystroke + Return in a single osascript
call to keep latency ~100ms per tap.
Fall back to pyautogui if osascript fails (handles edge cases) and
on non-macOS platforms.
ACR122U is a 13.56 MHz PC/SC (CCID) reader, not HID. Browsers can't
talk to PC/SC devices directly, so the kiosk JS can't see ACR122U
taps the way it sees a USB-HID reader.
This daemon bridges the gap:
- Polls the ACR122U via pyscard
- Reads UID via the standard ACS GET_UID APDU (FF CA 00 00 00)
- Types UID + Enter into the focused window using pyautogui
- Debounces re-reads of the same card (2s window)
Output format matches FusionClockNfcKiosk._normalize_uid() expectations:
colon-separated uppercase hex (04:10:5B:CA:FD:22:90 + Enter).
The kiosk JS already has a keyboard-wedge listener (v19.0.3.2.0+),
so no server-side or kiosk-side changes needed — wedge.py's
keystrokes route through the same handleTap() path as a USB-HID
reader, preserving photo verification + penalty + activity log.
Setup docs include macOS, Windows, Linux instructions plus
launchd/Task Scheduler/systemd snippets for running as a service.
Strategic value: with this, ACR122U deployments support UA-Pockets
(13.56 MHz DESFire EV3) for single-card door+clock setups in the
premium tier of the standard product kit. The 125 kHz EM4100 USB-C
HID reader remains the default tier.