Skip to content

Hammerspoon/Karabiner role does not reliably capture Caps Lock from external keyboards and overwrites Karabiner device state #151

@TechDufus

Description

@TechDufus

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.json contains a single complex modification mapping caps_lock to f13.
  • There is no device_if/device_unless condition in the checked-in rule.

The live Karabiner config currently matches the repo exactly:

  • ~/.config/karabiner/karabiner.json contains only the same minimal profile/rule.
  • It does not preserve a devices array 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 => Default
  • karabiner_cli --version-number => 150900
  • karabiner_cli --list-connected-devices includes both the built-in Apple keyboard and the external Keychron 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.yml
  • roles/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:

  1. Stop copying a full ~/.config/karabiner/karabiner.json from this repo.
  2. Ship the CapsLock rule as an asset under ~/.config/karabiner/assets/complex_modifications/ instead.
  3. If we need the rule enabled automatically, patch/merge only the relevant complex_modifications.rules entry rather than replacing the whole file.
  4. Validate with Karabiner-EventViewer on both:
    • built-in Apple keyboard Caps Lock
    • external Keychron Q5 HE Caps Lock
  5. If the Keychron Caps Lock event is genuinely different or unsupported, add a device-specific workaround or document the limitation.

Acceptance criteria

  • Built-in Apple keyboard Caps Lock triggers the Hammerspoon F13 summon path.
  • External Keychron Q5 HE Caps Lock triggers the same F13 summon path.
  • Provisioning no longer overwrites Karabiner's full stateful karabiner.json.
  • Re-running dotfiles -t hammerspoon does not erase Karabiner device state.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions