# globe-switch

Tap 🌐 to switch keyboard layouts on macOS instantly: a tiny always-on LaunchAgent that replaces the system Globe-key handler. Version 0.6.0, also on Homebrew.

[Open in the WASM portfolio](https://yemelianov.dev/?project=globe-switch&detail=1)

Tags: swift, macos, event-taps, carbon, launchd
Source: public
Repository: https://github.com/dmytro-yemelianov/globe-switch

globe-switch makes every tap of the 🌐 (fn) key switch keyboard layouts on macOS. It replaces the system Globe-key handler with a small LaunchAgent that stays running.

macOS routes the 🌐 key through TextInputSwitcher, an on-demand helper. launchd idle-kills it about six minutes after use (kernel `memorystatus … idle-exit`), even with about 10 GB of memory available; 53 relaunches were observed in three days. The next press only relaunches the helper and is lost. Its picker HUD also grabs the keyboard and swallowed letters typed right after a switch.

globe-switch uses a listen-only CGEventTap, so it can never block typing. A press counts as a clean tap only when fn/🌐 is released within 600 ms with no other key, modifier, media key, click or scroll while it is held, so fn+arrows and fn+E still work. A clean tap calls `TISSelectInputSource` to cycle the enabled keyboard layouts. The agent ships as GlobeSwitch.app and is started at login by a KeepAlive LaunchAgent, so launchd never idle-kills it; Login Items lists it as GlobeSwitch, and Input Monitoring is granted to the app rather than a bare binary. `install.sh` sets "Press 🌐 key to" to "Do Nothing" and restarts TextInputMenuAgent and TextInputSwitcher, because they cache that setting at launch; otherwise macOS keeps toggling too, and the first tap after a pause bounces back within 30–110 ms.

Every 30 seconds the agent checks its own Input Monitoring access: a revoked grant would leave the tap deaf without any error, so the agent restarts and waits for the grant instead. It also logs when 🌐 is no longer set to "Do Nothing". `globe-switch doctor` asks the running agent for its state, checks the permission, the 🌐 setting and the enabled layouts, and prints a fix for each problem. Logs go to the macOS unified log.

After each switch, every layout's flag lines up near the bottom of the screen with no background: the active one sits in the middle, larger and glowing in the accent colour, and the row slides with a spring animation to put the new layout there. Flags come from the country a layout is named after (British → 🇬🇧), otherwise from its language (Ukrainian → 🇺🇦, ABC → 🇺🇸). They are drawn as pixel art, sharp 22 × 14 pixel rectangles scaled without smoothing, one for each of the 259 regions macOS has a flag emoji for; territories get the flag macOS shows for them, and Nepal keeps its pennant shape. A soft 40 ms click plays with it, synthesized by the agent at a pitch of its own for each layout: 1.6 kHz for the first, then a major third, a fifth and an octave higher. The row is a panel that never becomes the key window or activates the app; a probe that types into its own field 0 and 40 ms after each tap confirmed that every letter arrived and focus never moved.

Settings live in the `dev.yemelianov.globe-switch` preferences domain and apply on the next press: the tap window, layouts to skip, `CycleOrder=recent` to go back to the previous layout like a quick Ctrl+Space, the flags (`ShowIndicator`, `LayoutFlags`) and the sound (`PlaySound`, `Sound`, `SoundVolume`). An earlier attempt registered the agent with `SMAppService`, but launchd resolved its program through LaunchServices by bundle ID, picked a removed Homebrew copy of the app and could not restart the agent, so the LaunchAgent keeps an absolute path.

The app is a Swift package of about 1,300 lines with no dependencies. Tap detection, layout choice, flags, the click and settings parsing are covered by 43 Swift Testing tests, and CI builds for Apple Silicon and Intel. The app is 760 KB, has a physical memory footprint of about 8 MB (about 15 MB once the flags and the click have been used) and uses 0% CPU when idle. It makes no network connections and sends no telemetry. It needs macOS 13 or later and an Apple keyboard with a 🌐 key, and was tested on macOS 27.2 on Apple Silicon.

Status: v0.6.0 is the current release, a source release under the MIT license ([GitHub release](https://github.com/dmytro-yemelianov/globe-switch/releases/tag/v0.6.0)). Install it with `brew install dmytro-yemelianov/tap/globe-switch` or `./install.sh` from a clone, then run `globe-switch install`; both build on your Mac, because the app is not notarized. It needs Input Monitoring permission, and `globe-switch uninstall` restores the 🌐 setting from before the first install.
