# In-Process Native HID Remapping ## User contract The app installs physical Caps->F19 mappings and requests any required input privileges under Capswitch's own identity. Never request that the user grant Input Monitoring or Accessibility to a system binary or helper. Do not launch /usr/bin/hidutil or other executables. ## Apple-supported mechanism Technical Note TN2450 documents: - IOHIDEventSystemClientCreateSimpleClient - IOHIDEventSystemClientCopyServices - IOHIDServiceClientConformsTo(kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard) - IOHIDServiceClientCopyProperty/SetProperty with UserKeyMapping. The IOKit adapter is compiled inside the existing CapswitchHID SwiftPM C target, linked to IOKit in the Capswitch executable. ## Safety and lifecycle - Set up active Quartz event tap before changing HID keyboard mappings. - Enumerate connected keyboard services, read each current UserKeyMapping, and fail closed if malformed or conflicting with Caps/F19. - Preserve other mappings while appending Caps->F19 only when absent. - Verify the result per service, do not trust a successful setter alone. - Persist owned service registry IDs under Capswitch's own preferences so a crash/restart can restore exactly Capswitch-owned rules. - On Pause/Quit, remove only the exact pair for those owned services. - A disconnected service's mapping is discarded by macOS. - On wake/reconnect, re-enumerate and reconcile. - No privileged helpers, keyboard drivers, or external subprocesses. - Both Accessibility and Input Monitoring requests target Capswitch; the OS remains responsible for user consent. ## Acceptance tests - Build without Xcode IDE using Taskfile and Command Line Tools on macOS 14.8.9. - First launch creates permission prompts where appropriate. - Native --doctor reports keyboard services and applied pair counts. - Short Caps taps between typed letters register as F19 down/up and switch exactly once on release. - Option+Caps toggles real Caps Lock including actual typing state. - Pause/Quit preserves foreign HID remappings. - Keyboard reconnect / sleep recovery works.