A minimal, auditable macOS menu-bar utility that selects a configured keyboard input source when an application becomes frontmost.
The project is intentionally narrow:
- exact per-app bundle-ID rules plus one default rule;
- switch on activation, not a continuous layout lock;
- App Sandbox enabled;
- no Accessibility or Input Monitoring permission;
- no keyboard event taps, clipboard access, browser inspection, or telemetry;
- no networking, updater, subprocesses, or third-party dependencies.
Per-app input-source switching is not new. The reason this implementation exists is to provide a small source-first option whose security boundaries are easy to inspect and mechanically test. See docs/comparison.md for the positioning against related projects.
We reviewed the main open-source alternatives before implementing this project:
- LockIME is the closest and most full-featured option, but it is designed for continuous locking, per-URL rules, optional Accessibility-powered features, and Sparkle updates. We needed a one-shot per-app switcher with a much smaller runtime and audit surface.
- TypeSwitch has a strong per-app rules model, but its application architecture includes several third-party packages, Sparkle, and a LaunchAgent fallback. Our requirements excluded updaters, subprocesses, custom LaunchAgents, and external dependencies.
- Input Source Pro intentionally covers a broader product surface, including indicators and browser-aware features. Those capabilities require permissions and APIs that this project deliberately avoids.
- KeyMinder and SwitchKey use broader permission or login-item approaches than we wanted, while macism is a useful command-line building block rather than a per-app menu-bar service.
This is a requirements decision, not a claim that those projects are inferior. Choose them when their UI and broader features are useful. This implementation exists for environments where the priority order is: no TCC permissions, App Sandbox, no network or updater, no third-party dependencies, deterministic switching, and source that can be audited quickly.
On each application activation, the app:
- waits for a 40 ms cancellable debounce;
- re-reads the actual frontmost application's bundle identifier;
- resolves an exact rule, an ignored app, or the configured default;
- switches only if the desired input source differs;
- verifies once after 100 ms and may retry once.
Delayed work is cancelled and generation-checked, so a retry from an earlier app cannot overwrite the rule for a newer frontmost app. After the one verification, the helper goes idle. A manual layout change inside the same app is respected until that app is activated again.
The first-run configuration is deliberately disabled, contains no
application rules, and knows only the standard U.S. layout identifier. A fresh
build does not change the keyboard layout until you inspect the generated JSON,
add your own identifiers, set "enabled": true, and reload it.
The helper always ignores its own activation, independently of the JSON file.
- macOS 13 or later
- Xcode Command Line Tools or full Xcode
- Swift 6 toolchain
No Homebrew packages or external Swift packages are required.
make test # deterministic unit and stress tests
make audit # static scan for forbidden APIs
make app # release .app, ad-hoc signed, App Sandbox enabled
make verify # signature, entitlement, linkage, and string checks
make install # copy the app to ~/ApplicationsLaunch the locally built app:
open "$HOME/Applications/AppInputSourceSwitcher.app"The repository is source-only. It does not currently publish prebuilt, notarized, or auto-updating binaries.
List the enabled keyboard input sources on your Mac:
make diagnosticDiscover exact application bundle identifiers rather than guessing them:
./scripts/discover-app-bundle-ids.sh "Telegram" "Visual Studio Code"On first launch the app creates:
~/Library/Containers/org.appinputsourceswitcher.AppInputSourceSwitcher/Data/Library/Application Support/AppInputSourceSwitcher/config.json
The Open Configuration Folder menu item is the safest way to find it. Start
from config/config.example.json, replace every
placeholder with diagnostic output from your Mac, then set "enabled": true.
Choose Reload Configuration from the menu.
Configuration validation rejects duplicate bundle IDs, unknown aliases, empty identifiers, unsupported versions, and out-of-range timings. A failed reload never overwrites the file or replaces the last valid in-memory configuration.
The menu's Launch at Login item uses SMAppService.mainApp. It does not
write a custom plist to ~/Library/LaunchAgents and has no shell fallback. If
macOS reports that approval is required, review the item under System Settings >
General > Login Items.
The installed executable also supports:
APP="$HOME/Applications/AppInputSourceSwitcher.app/Contents/MacOS/AppInputSourceSwitcher"
"$APP" --launch-at-login-status
"$APP" --enable-launch-at-login
"$APP" --disable-launch-at-loginDisable launch at login before deleting the app.
The app uses unified logging. Records contain only bundle identifiers, input-source identifiers, outcomes, and durations:
log show --last 1h \
--predicate 'subsystem == "org.appinputsourceswitcher.AppInputSourceSwitcher"' \
--info --debugSee SECURITY.md for the full policy and docs/architecture.md for implementation details.