Skip to content

Repository files navigation

MagicKeys

Turn a spare Apple Magic Keyboard into a macropad. Click a key in the UI, assign it an action, and that key — on the Magic Keyboard only — launches apps, opens URLs, runs shell commands, Apple Shortcuts, or sends keystrokes. The MacBook's built-in keyboard is never touched.

Triggers can be bare keys (T) or combos (⇧T, ⌘⇧F8, …) — toggle fn/⌃/⌥/⇧/⌘ chips in the key editor to edit each combo slot; one physical key can hold several mappings. The kill unassigned keys switch (02 / board) makes every unmapped key do nothing at all (no stray letters); modifier caps stay live so combos keep working.

MagicKeys

Requirements

  • macOS 12+ with Karabiner-Elements installed
  • Node.js 20.19+ (or 22.12+)
  • A Magic Keyboard you can dedicate to macros — any model, Bluetooth or USB

Run

git clone https://github.com/stearyl/magickeys.git
cd magickeys
npm install
npm start        # build + serve → http://localhost:4477
npm run dev      # dev mode (vite on :5177, api on :4477)

The app only needs to be running while editing mappings. The mappings themselves live inside Karabiner-Elements and keep working with MagicKeys closed.

Dock icon

~/Applications/MagicKeys.app — drag it into the Dock. Clicking it starts the server if it isn't running (finds node via fnm/homebrew/nvm even in the Dock's bare environment, builds dist/ if missing, logs to ~/.config/magickeys/launcher.log) and opens the UI as a chromeless Chrome app window. Rebuild/reinstall it any time with npm run install-app; icon source lives in assets/icon.html (regenerate via headless-Chrome screenshot → iconutil).

How it works

MagicKeys writes complex modification rules into ~/.config/karabiner/karabiner.json. Every rule carries a device_if condition pinned to the Magic Keyboard's vendor/product id (detected via hidutil list), which is what keeps the built-in keyboard separate. Karabiner watches the file and reloads automatically — no restarts.

  • Rules are tagged with a MagicKeys: description prefix; anything else in karabiner.json (other rules, settings, profiles) is preserved verbatim.
  • Every real write is preceded by a timestamped backup → ~/.config/magickeys/backups/ (last 20 kept).
  • App state (chosen device + mappings) lives in ~/.config/magickeys/config.json; karabiner.json is regenerated from it, so the two can always be re-synced ("re-apply" in the UI).
  • Writes are atomic (tmp file + rename).

Action types

type karabiner to
launch app shell_command: open <path>
open URL shell_command: open <url> (any scheme — https:, raycast:, …)
shell shell_command verbatim
send keys key_code + modifiers
apple shortcut shell_command: shortcuts run '<name>' (picker fed by shortcuts list)
app menu item shell_command: osascript … click menu item "Save…" of menu "File" of menu bar 1
disable key_code: vk_none

Combo triggers compile to from.modifiers.mandatory (generic names, so either shift key works). Mapping ids are canonical strings — p, shift+t, shift+command+f8 — in fn→⌃→⌥→⇧→⌘ order. "Kill unassigned keys" appends one catch-all rule after all mappings: every non-modifier key (plus the top-row media events) with optional: any modifiers → vk_none, so anything an earlier rule didn't claim is swallowed.

F-key top row

The media glyphs (F1–F12) don't send F-key codes unless fn is held — they arrive as consumer/apple-vendor events. Each F-key mapping therefore includes extra manipulators matching the media variant (brightness, mission control, play/pause, volume, …), so the key fires in either mode. F4 covers both the 2015 (launchpad) and 2021 (spotlight) models. F5/F6 only match as plain F-keys.

App menu actions (the "menu" type)

Binds any menu command of any app — e.g. TextEdit's File → Save… — including commands with no keyboard shortcut. The editor has a live menu browser (pick a running app, drill into its menus); at trigger time the action optionally activates the app, then clicks the item via System Events. Deep submenu paths work (Format → Font → Bold).

Permissions (one-time, macOS requires them for UI scripting):

  1. Browsing/testing from the MagicKeys UI — the app you run MagicKeys from (your terminal) needs: the Automation → System Events consent (macOS prompts; click Allow) and Privacy & Security → Accessibility enabled (no prompt — toggle it manually).
  2. Key presses — Karabiner runs the click, so the first press pops an Automation prompt for karabiner_console_user_server; allow it. If clicks still do nothing, also add karabiner_console_user_server (in /Library/Application Support/org.pqrs/Karabiner-Elements/bin/) or Karabiner-Elements to the Accessibility list.

Menu names must match exactly — Apple uses the single ellipsis character (Save…, not Save...); the browser always captures names exactly, so prefer it over manual typing.

Troubleshooting

  • Mapping doesn't fire → Karabiner-Elements Settings → Devices: make sure Modify events is enabled for the Magic Keyboard. Check EventViewer to confirm Karabiner sees the key.
  • Device not detected → it must be connected (Bluetooth or cable); hit rescan. Worst case, enter vendor/product ids manually (EventViewer shows them).
  • Restore an old config → copy a file from ~/.config/magickeys/backups/ over ~/.config/karabiner/karabiner.json. Karabiner also keeps its own backups in ~/.config/karabiner/automatic_backups/.
  • Uninstall → remove all mappings in the UI (or delete the MagicKeys:-prefixed rules from karabiner.json), then delete ~/.config/magickeys/.

Stack

Vite + React + TypeScript + Tailwind v4 front, Express 5 + tsx back, bound to 127.0.0.1:4477. No database — karabiner.json is the runtime state, ~/.config/magickeys/config.json the source of truth. npm run typecheck && npm run build is the verification gate.

License

MIT © Stearyl

Not affiliated with Apple Inc. “Magic Keyboard” is a trademark of Apple Inc. Powered by Karabiner-Elements.

About

Turn a spare Apple Magic Keyboard into a macropad — local web UI that writes device-pinned Karabiner-Elements rules

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages