-
Notifications
You must be signed in to change notification settings - Fork 64
Hammerspoon/Karabiner role does not reliably capture Caps Lock from external keyboards and overwrites Karabiner device state #151
Description
Summary
With an external keyboard attached, pressing that keyboard's Caps Lock is not reliably being intercepted by Karabiner for the Hammerspoon summon/macros flow.
The repo currently deploys a minimal ~/.config/karabiner/karabiner.json that maps caps_lock -> f13, but it does so by replacing Karabiner's full state file. That is brittle because Karabiner stores device/runtime state in the same file.
Expected
Any physical Caps Lock key, whether from the built-in Apple keyboard or an attached external keyboard, should be handled by Karabiner and produce the same F13 trigger used by Hammerspoon summon/macros.
Actual
Built-in keyboard flow is expected to work, but an attached external keyboard's Caps Lock is not reliably being "stolen" by Karabiner for the same F13 path.
Evidence
Repo-side Karabiner config is global, not device-scoped:
roles/hammerspoon/files/karabiner/karabiner.jsoncontains a single complex modification mappingcaps_locktof13.- There is no
device_if/device_unlesscondition in the checked-in rule.
The live Karabiner config currently matches the repo exactly:
~/.config/karabiner/karabiner.jsoncontains only the same minimal profile/rule.- It does not preserve a
devicesarray or other Karabiner-managed state.
Karabiner sees the external board:
{
"manufacturer": "Keychron",
"product": "Keychron Q5 HE",
"transport": "USB",
"device_identifiers": {
"is_game_pad": true,
"is_keyboard": true,
"is_pointing_device": true,
"product_id": 2896,
"vendor_id": 13364
}
}Additional runtime checks:
karabiner_cli --show-current-profile-name=>Defaultkarabiner_cli --version-number=>150900karabiner_cli --list-connected-devicesincludes both the built-in Apple keyboard and the externalKeychron Q5 HE
Why this repo is probably part of the problem
Even if the immediate external-keyboard failure turns out to be hardware-specific, this repo is managing Karabiner the wrong way.
roles/hammerspoon/tasks/MacOSX.yml currently copies a full karabiner.json into place:
roles/hammerspoon/tasks/MacOSX.ymlroles/hammerspoon/files/karabiner/karabiner.json
Karabiner's config file is stateful. Replacing it with a stub means we throw away any device-specific state Karabiner wants to maintain, which is exactly the wrong failure surface once multiple keyboards exist.
Proposed fix
Stop treating karabiner.json as a static dotfile.
Preferred direction:
- Stop copying a full
~/.config/karabiner/karabiner.jsonfrom this repo. - Ship the CapsLock rule as an asset under
~/.config/karabiner/assets/complex_modifications/instead. - If we need the rule enabled automatically, patch/merge only the relevant
complex_modifications.rulesentry rather than replacing the whole file. - Validate with Karabiner-EventViewer on both:
- built-in Apple keyboard Caps Lock
- external
Keychron Q5 HECaps Lock
- If the Keychron
Caps Lockevent is genuinely different or unsupported, add a device-specific workaround or document the limitation.
Acceptance criteria
- Built-in Apple keyboard
Caps Locktriggers the HammerspoonF13summon path. - External
Keychron Q5 HECaps Locktriggers the sameF13summon path. - Provisioning no longer overwrites Karabiner's full stateful
karabiner.json. - Re-running
dotfiles -t hammerspoondoes not erase Karabiner device state.