Skip to content

This is the multi-page printable view of this section. .

Return to the regular view of this page.

Blog

Product notes, engineering write-ups, and release notes for Capslock.

Why Capslock works the way it does, how it is built, and what changed in each version.

1 - Posts

Product notes and engineering write-ups.

1.1 - Capslock goes native

Capslock started as a Karabiner-Elements configuration. It is now a native Mac app that keeps the same layout and adds an editor, app rules, lessons, and a way to always get Caps Lock back.

For years Capslock was a configuration file. You installed Karabiner-Elements, imported a set of rules, and Caps Lock became Escape on a tap and a Hyper key on a hold. With Command, Option, Control and Shift it reached sixteen planes of shortcuts for the cursor, editing, windows, apps and the mouse.

That worked, and it kept two costs. Karabiner-Elements had to be installed, with its driver and its permissions, just to change one key. And the layout was a JSON file: to change a binding you edited that file, and to learn the layout you kept a picture of the keyboard open.

The new Capslock is a native macOS app. It is built for the same layout, and for removing those two costs.

The same layout, nothing to install around it

Capslock ships the complete Capslock Classic layout: 357 bindings, from ⇪ H J K L for the arrows to five extra clipboards on the number row. Every rule of the last Karabiner version has a native counterpart, and none was dropped. App launches, sleep, and the clipboard slots used to be shell commands; they are now built-in actions, so the default layout runs no scripts at all.

The app needs one permission, Accessibility. It installs no kernel extension, driver, daemon, or privileged helper. See How Capslock works for how that is possible.

An editor instead of a JSON file

The Keyboard page draws your keyboard with every action on its key. Click a key to rebind it, disable it, put back the Classic default, or see its YAML. A mapping can carry app rules, so ⇪ N can delete a word everywhere and send ⌃W in Terminal. Every change is applied as one transaction, and the configuration is still a plain YAML file you can export, import, and keep in version control.

Shell commands remain possible and need your approval before they run. A command that arrives in an imported file is switched off until you review it.

Learning built in

Hold Caps Lock and wait, and the current plane appears on screen. The Playground has short lessons that follow your own mappings, a Safe Practice mode that previews actions with side effects instead of running them, and a Free Play view that shows what the last key did without recording what you type.

Always a way back

A tool that owns your Caps Lock key has to give it back reliably. Capslock changes only the one key mapping it adds, restores it on Pause and Quit, and leaves other tools' mappings alone. A small helper restores it after a crash. If Karabiner-Elements or another tool already uses Caps Lock, Capslock does not start, and Diagnostics says what to change.

Two ways to be Hyper

The Karabiner layout made Caps Lock look like ⌃⌥⇧⌘ to every app, and many people built Alfred, Raycast or Moom shortcuts on that. Capslock keeps it as Classic mode, the default. Native mode keeps Hyper private to Capslock, for those who would rather other apps never saw it.

Where it stands

Capslock 0.4.0 is a release candidate. The download goes up once it is Developer ID signed, notarized, tested on a clean account and on Intel and macOS 14, and its update feed is verified. Until then the documentation describes the app as it will ship, and the Download page has its status.

The Karabiner-Elements configuration stays available, free, as the Karabiner Edition.

1.2 - How Capslock works: F18, an event tap, and a mapping it gives back

Caps Lock is a poor modifier as macOS delivers it. Capslock remaps it to F18, reads it from an active event tap, and changes only the part of the system key mapping it owns.

Caps Lock sits in the best spot on the keyboard and does the least useful job. Capslock gives it a new one: tap it for Escape, hold it for Hyper, and combine it with the four modifiers to reach sixteen control planes. This post explains how a native app does that on macOS without a kernel extension, a driver, or a privileged helper.

Caps Lock is not a modifier

macOS does not report Caps Lock as a key that goes down and comes back up. It reports a lock state that flips, and it adds a short delay so that brushing the key does not toggle it. That is fine for typing capitals and useless for a modifier that must be held.

So Capslock starts with a remap. At activation it asks hidutil to send the physical Caps Lock as F18, a key that no Apple keyboard has. F18 arrives as an ordinary key-down and key-up pair, which the app can treat as "held" and "released".

F18 has a second advantage: it is not one of the system modifiers. Holding Caps and Shift and pressing H arrives as F18 held, Shift held, H. Nothing in macOS combines F18 with Shift into something else, so all four modifiers stack on top of Caps for free.

Reading keys from an event tap

Capslock installs a CGEventTap at the HID level, at the head of the queue, as an active tap. Active means it can change or drop events, not just observe them. macOS gates that on the Accessibility permission. It does not need Input Monitoring, which only applies to listen-only taps.

If the permission is missing when the app starts, it keeps retrying. The tap installs the moment you grant access, without a relaunch. The tap runs on its own thread, and its callback does little more than compare integers, so typing does not slow down.

Resolving a chord

When F18 goes down the app records that Caps is held and swallows the event. When F18 comes back up it decides what the press was:

  • A tap, with no other key pressed, runs the tap action. In the default profile that is Escape.
  • A chord, Caps plus another key, runs the action mapped to that key on the current plane, and the original key never reaches the app in front.
  • A double tap, when a mapping defines one, runs that instead. The single tap waits for the double-tap window so the two cannot both fire.

The plane comes from the modifiers you hold with Caps. Each modifier is a bit: Command 1, Option 2, Control 4, Shift 8. Their sum names one of sixteen planes, from 0 for Caps alone to 15 for all four. Matching is exact. A chord with an extra modifier never falls back to a less specific plane, so adding Shift to a navigation key can mean "select" without surprising you elsewhere.

Every event Capslock synthesizes carries a marker, and the tap skips events that carry it. Without that, the arrow key Capslock sends for Caps+H would come straight back through the tap.

Two ways to be Hyper

Earlier Capslock layouts implemented Hyper by holding the four right-side modifiers. Launchers and window managers such as Alfred and Moom listen for that combination, and many people have shortcuts built on it.

Capslock keeps this as a choice rather than an implementation detail:

  • Classic Compatibility holds right Command, Option, Control and Shift for as long as Caps is down, so other apps see Hyper as before.
  • Native Clean keeps Hyper private. Other apps see nothing while Caps is held, only the actions it produces.

In both modes the plane is computed from the modifiers you physically press. The right-side keys Capslock holds for compatibility are excluded, or every chord would land on plane 15.

Owning only what it changed

hidutil property --set replaces the whole user key mapping. The obvious implementation writes its own list on start and clears the property on quit, which deletes any mapping another tool or the user had set. Capslock treats the existing mapping as somebody else's:

  1. It reads the current mapping and refuses to start if another entry already remaps Caps Lock.
  2. Before changing anything it writes a recovery journal with the previous mapping and the single entry it is about to add.
  3. It merges only its own entry and reads the result back to confirm it.
  4. On Pause or Quit it removes only that entry and restores the previous mapping, unless something else changed it in the meantime.

If another program changes the mapping while Capslock runs, Capslock stops and reports a conflict instead of fighting over the last write. It checks again after sleep and wake, when a keyboard is plugged in, and when the user session changes. This is also why Pause gives you a real Caps Lock back, not an F18 that is merely passed through.

When the app does not quit cleanly

A crash or a forced quit skips the clean-up in step 4. Two things cover that case. A small watchdog embedded in the app reads the recovery journal and watches its parent process; if the app exits without releasing the mapping, it restores the previous one. The next launch also reconciles any journal it finds before it installs the tap.

If you ever need to clear the mapping by hand, use the app's own command, which removes only the Caps Lock entry it added:

/Applications/Capslock.app/Contents/MacOS/Capslock --restore-original-caps-lock

Do not reset the mapping with an empty list. That is global and would remove every other tool's remapping too. The mapping does not survive a restart in any case.

What stays local

Everything above happens on your Mac. Capslock has no analytics or advertising SDK and does not send keys, mappings, clipboard contents, or usage data anywhere. Its diagnostic log leaves out typed text, clipboard contents, Shell output, and app history. The privacy page lists what is stored and when the app uses the network.

2 - Release notes

What changed in each Capslock version.

Versions 0.1.0 to 0.3.0 were internal development builds. 0.4.0 is the first release candidate; see Download for its status.

2.1 - Capslock 0.4.0

A clearer first run, lessons that follow your configuration, Profile behaviour for unmapped keys, verified recovery feedback, and plain statements of what stays local.
Release candidate

0.4.0 is in candidate acceptance. It has not been signed, notarized, or published for download yet. See Download for the current status.

What's new

  • A clearer first run. Onboarding ends on the complete keyboard. You can browse it before granting keyboard access, and the app says plainly whether it is running your keyboard or only showing a preview.
  • Lessons follow your configuration. The first Playground chapter now includes Hyper+Space. If you changed or disabled a key a lesson uses, the lesson says so. Safe Practice previews are marked differently from actions that really ran.
  • Profile behaviour travels with your mappings. Choose what unmapped keys do and which modifier sides select a plane. An explicit pass-through is kept apart from a removed binding, and both survive Undo and export.
  • Recovery reports what it verified. Restoring Caps Lock reports success only after the system mapping has been read back. When the mapping cannot be read, ownership is shown as unknown instead of guessed.
  • Clear local-data and trust boundaries. Statistics explain what a trigger count is and how long it is kept. Shell approval explains that a command runs with your user privileges, may use the network, and is not undone by Pause or Undo.

Since the candidate was cut

Work on 0.4.0 continued after the candidate was cut, and the following fixes are part of the same version:

  • The interface was redesigned, with shorter copy, a new ⇪ app icon, and Mac key legends throughout.
  • A first run takes over nothing until you finish onboarding or press Start Capslock.
  • A recovery record left by an earlier boot no longer blocks activation after an unclean shutdown.
  • A newly attached keyboard gets its Caps Lock mapping without Input Monitoring permission.
  • Hyper+Escape toggles Caps Lock once per press instead of on every key repeat.
  • Editing the configuration file outside Capslock is detected: the app reloads the file instead of overwriting your change.

2.2 - Capslock 0.3.0

Safer keyboard recovery, stricter configuration handling, more reliable pointer controls, and editor and diagnostics fixes. Internal build.
Internal build

0.3.0 was a development build and was not distributed publicly.

  • Safer keyboard recovery. Reconnecting a keyboard checks for remapper conflicts again. Differing per-device mappings are preserved instead of merged, and an explicit Pause cancels pending wake-up retries.
  • Stronger configuration handling. Malformed numbers, duplicate document sections, and invalid fallback actions are rejected safely. An empty profile can be exported and restored, and a failed statistics write is retried.
  • More reliable pointer controls. Continuous movement follows the real cursor position, and releasing a chord cancels pending movement and scrolling.
  • Editor and diagnostics. Menus follow language changes, and a diagnostics export can replace an existing archive.
  • Compatibility. Mappings that reference a retired built-in action still load, and preview mode cannot change permissions or login items.

2.3 - Capslock 0.2.0

The complete Classic layout becomes the default, the editor puts all sixteen planes and the whole keyboard on one screen, and the Caps key gets explicit semantics. Internal build.
Internal build

0.2.0 was a development build and was not distributed publicly.

  • The complete Classic layout is the default. A fresh start loads every binding of the Capslock Classic profile instead of a minimal starter map.
  • A keyboard-first editor. The default view shows the sixteen named planes, the full main keyboard, and the numeric keypad together, with each mapped action written on its key.
  • Explicit Caps semantics. Tapping Caps sends Escape. Hyper+Space sends the real Control-Space input-source shortcut. Hyper+Escape toggles the real Caps Lock.
  • English and Chinese. The app follows the system language, ships complete English and Simplified Chinese text, and falls back to English for other languages.
  • Auditable safety. Imported executable actions lose their trust until you review them, and diagnostics and recovery never change Karabiner-Elements.

2.4 - Capslock 0.1.0

The first native build: Capslock Classic built in, two Hyper modes, a native editor with per-app overrides, Hold-to-Reveal, the Playground, and diagnostics. Internal build.
Internal build

0.1.0 was a development build and was not distributed publicly.

  • Capslock Classic built in. Every behaviour of the Classic profile has an explicit native counterpart, resolved exactly across sixteen planes.
  • Two Hyper modes. Classic Compatibility holds the right-side modifiers for Alfred, Moom, and existing shortcuts; Native Clean keeps the control layer private to Capslock.
  • Native editor and app overrides. Inspect, rebind, disable, restore, validate, and undo or redo a key, and scope an action to one app by its exact bundle ID, without editing JSON by hand.
  • Hold-to-Reveal and Playground. See the current named plane while you hold Caps, learn tap, hold, Hyper+Escape and H/J/K/L, then explore in Free Play.
  • Safe Practice and recovery. Lessons block dangerous effects, and losing focus returns to live mode. Diagnostics can release synthetic input, restore Caps Lock, and export a redacted report.
  • Coexistence by default. Existing non-Caps hidutil mappings are kept, a Caps or F18 conflict stops activation, and Karabiner-Elements is never installed or reconfigured.