Skip to content

Repository files navigation

Rollover — Keyboard Layout Switcher

Rollover

Keyboard Layout Switcher

macOS menu bar app that switches the keyboard input source automatically based on which physical keyboard is connected — e.g. one layout for an external keyboard, another for the built-in one, applied the moment you plug in or unplug.

Adds a per-keyboard layout picker in the menu bar and Settings, and remembers every keyboard it has ever seen — including ones that aren't currently plugged in. Requires no Input Monitoring or Accessibility permission; reads IOKit registry properties only, never opens a device.

See CLAUDE.md for the hard constraints a future change must not break, and PRODUCT.md / DESIGN.md for what the app is for and how it is meant to look. Implementation reasoning lives in comments next to the code it explains.

Features

  • Menu bar shows the active layout by name; menu lists every connected keyboard with a submenu to assign its layout, applied immediately
  • Ordered priority list (Settings): the highest-ranked connected keyboard with an assigned layout wins; otherwise a fallback layout applies
  • Remembers every keyboard it has seen, connected or not, keyed by vendor/product ID and product name
  • Detects layout switches made outside the app (system input menu) and offers to remember them for the currently connected keyboard
  • Launch-at-login toggle (SMAppService), on by default
  • Re-asserts the correct layout on sleep/wake

What it looks like

Real screenshots, dark appearance. The status item carries the keycap mark when the layout name is switched off in Settings; with it on, the mark gives way to the name.

The Rollover menu bar item with its menu open: the active layout, the connected keyboards with their assigned layouts, and Settings and Quit

The Rollover Settings window: the app identity header, the priority-ordered keyboard list with per-keyboard layout assignment and connection state, and the Ignored section

Installing

There is no notarized download. This project is distributed as source: you build it, so the copy you run was never downloaded and macOS never quarantines it. That is a deliberate choice — see below.

git clone https://github.com/nicks-passiert/rollover.git && cd rollover
./scripts/build-app.sh
cp -R build/Rollover.app /Applications/
open /Applications/Rollover.app

You need the Xcode Command Line Tools (xcode-select --install). Full Xcode is not required. No Apple ID, no developer account, no certificate — the script signs ad-hoc when it finds nothing better.

Reinstalling over a running copy: quit the running app first. macOS does not stop a live process when its bundle is replaced, so the old build keeps running and you end up looking at the previous version's windows, wondering why your change did not take. Quit it from the menu bar, or:

pkill -f 'Rollover.app/Contents/MacOS'

Your remembered keyboards survive a reinstall — they live in Application Support, not in the bundle.

On first launch the app registers itself as a login item, so expect macOS to tell you a background item was added. It will also ask for notification permission, used only for the banner when a layout switches and the "remember this?" prompt. Deny it and the app works fine, silently.

Ad-hoc signing does not appear to cost you either of those. Tested on macOS 26: replacing a certificate-signed install with an ad-hoc build left the login item enabled, and a fresh SMAppService registration under an ad-hoc signature returned enabled with no approval step. macOS keys these on the bundle identifier and path rather than the signature hash. Notification permission showed no sign of resetting either, though that half was observed rather than deliberately provoked.

Why there is no download

macOS attaches the com.apple.quarantine attribute when a file arrives from a browser, AirDrop, Mail, or Messages — not when you compile it locally. Quarantine is what makes Gatekeeper evaluate an app, and Gatekeeper refuses anything not signed with a Developer ID Application certificate and notarized by Apple. Since macOS 15 the old Control-click → Open shortcut is gone: a user has to visit System Settings → Privacy & Security and click Open Anyway past a warning that the app cannot be checked for malicious software.

Developer ID requires a paid Apple Developer Program membership, which this project does not have. That leaves two honest options and one dishonest one. Building from source is the honest option taken here.

The dishonest one is shipping a binary with xattr -d com.apple.quarantine in the install instructions. It works, and it teaches people to strip quarantine from downloaded binaries — the exact habit that gets them compromised later. An app whose entire pitch is that it reads no keystrokes and needs no Input Monitoring permission has no business asking anyone to disable a security check to run it.

The App Store is not a third option regardless: TISSelectInputSource does not work inside the App Sandbox, so the app is deliberately unsandboxed.

If that ever changes

The build script already supports the notarized path, so enrolling is the only missing piece:

./scripts/build-app.sh --notarize

It signs with Developer ID, submits to Apple, waits for the verdict, staples the ticket into the bundle, re-zips so the archive carries it, and asks Gatekeeper for its own verdict via spctl. It needs a Developer ID Application certificate and one-time stored credentials:

xcrun notarytool store-credentials "rollover" \
    --apple-id "you@example.com" \
    --team-id "YOURTEAMID" \
    --password "app-specific-password"

The password is an app-specific password from appleid.apple.com, not your Apple ID password. Override the profile name with NOTARY_PROFILE=…. The script fails with instructions if either prerequisite is missing. This path has never been run end to end.

Build details

scripts/build-app.sh compiles, assembles the bundle, and signs it. The Command Line Tools are enough — no Xcode installation required — and --help lists the options.

It picks a signing identity in this order, so a fresh clone builds whether or not you have an Apple account:

Identity When Good for
$SIGN_IDENTITY you set it choosing explicitly
Developer ID Application --notarize anyone's Mac
Apple Development default, if present your Mac
ad-hoc (-) nothing else found your Mac

Hardened runtime is applied on every path — codesign -dv must show flags=0x10000(runtime), or 0x10002(adhoc,runtime) when signing ad-hoc. Without it any same-user process could inject code via DYLD_INSERT_LIBRARIES into something that runs at every login.

Versions and releases

Releases are tagged v1.2.3 and listed on GitHub, with notes taken from CHANGELOG.md. main is kept buildable between them; the tags exist so you can build from a known state rather than from whatever landed last.

The version you see in Settings → About is two numbers: the release, from the tag you built, and in brackets the build — the count of commits in your checkout. Quote both when reporting a problem; the second identifies the exact commit.

No release carries a binary. That is deliberate — see "Why there is no download".

Verifying a build

Worth walking once after building from source, or after a change to switching, signing, or the login item. Steps 3 and 5 are the ones that catch real regressions.

  1. codesign -dv build/Rollover.app reports flags=0x10000(runtime), or 0x10002(adhoc,runtime) when signed ad-hoc.
  2. Launch it. The status item shows the active layout's name, and the menu lists every connected keyboard.
  3. Assign a layout to an external keyboard, then unplug it. The menu bar flips to the fallback (or to the next connected keyboard that has one), and back on replug. Cross-check with defaults read com.apple.HIToolbox AppleCurrentKeyboardLayoutInputSourceID.
  4. Assign a layout from the menu submenu. It applies immediately and survives a relaunch.
  5. Switch layout by hand from the system input menu. A "remember this?" prompt appears, attributed to the highest-ranked connected keyboard. Then confirm a switch the app makes produces no prompt — that suppression window is the only thing stopping a feedback loop.
  6. Toggle Launch at login, then log out and back in. The app is running, and the toggle still reads on.
  7. Sleep and wake the Mac. The layout is re-asserted.

Status

Core switching behavior is implemented and verified end to end: build and sign, connect/disconnect, manual-layout-switch detection, login item across reboot, sleep/wake. Name, icon, and About are in place; nothing is knowingly missing.

Two things worth knowing about how this is put together:

  • There is no About panel, by design. LSUIElement plus MenuBarExtra means the app has no app menu, so there is no standard "About Rollover" item and no panel to open. Settings carries it instead: identity at the top of the window, provenance at the bottom.
  • There is no update check. The app never opens a network connection. Pull the repository and run the build script again.

Licence

GPL-3.0-or-later — see LICENSE. Use it, study it, modify it, redistribute it. If you distribute a modified version, it has to carry the same licence and ship its source, so whoever receives it gets what you got.

That obligation is on distribution, not on use. Change it however you like for yourself and you owe nobody anything.

Worth being clear about what copyleft does and does not cover, because it is routinely overstated: it protects this code, not the idea behind it. The part most worth learning from — the approach in KeyboardMonitor.swift, which identifies keyboards from IOKit registry properties without ever opening a device, and so needs no Input Monitoring or Accessibility permission — is a technique, and techniques are not copyrightable. Read it, understand it, and implement it yourself in whatever you like under whatever licence you like. The licence only asks that you not take this code itself proprietary.

Two things sit outside the grant: the application icon in Resources/AppIcon.iconset/, and the name Rollover. Both are artwork and identity rather than code. If you ship a fork, please give it your own name and your own mark — not a trademark claim, just the ordinary courtesy that keeps two different apps from looking like the same one.

About

Rollover — Keyboard Layout Switcher. A macOS menu bar app that switches the keyboard input source to match the keyboard you are typing on, with no Input Monitoring or Accessibility permissions.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages