LockIME: one disk image per architecture, URL rules ordered by dragging, and a dash that means undocumented
A native macOS menu-bar app that keeps your keyboard input source locked — global, per-app, and per-URL.
At a glance
- What is it?
- oomol-lab/LockIME is a GPL-3.0 Swift menu-bar app for macOS 14 and later that re-applies a locked input source the moment you or another program change it, with rules scoped globally, per application, or per browser URL.
- Who is it for?
- Worth the setup if you fight input-source drift on a daily basis and actually use the per-site rules, since the lock-versus-switch distinction is what the two open-source alternatives do not give you. Before switching, confirm three things: how many games or tools will need their own Ignore rule after the first Conflict detected entry, whether any two programs you run share an executable name, and whether the Intel disk image you grab is the native build or the generic one.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- oomol-lab/LockIME623 ★ · +33
| Repository | 2026-09-18 | 2026-11-14 |
|---|---|---|
| oomol-lab/LockIME | 590 | 623 |
33 Stars in the last 57 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Two disk images instead of one, and a cask that picks for you
Install runs through one command:
brew install --cask oomol-lab/tap/lockimeInstall it by hand and the choice is yours: the app ships as two separate disk images, an `-arm64` build for Apple silicon and an `-x86_64` build for Intel, and there is no universal binary. The stated reason is download size, which the project puts under 3 MB for the whole app against roughly 7.6 MB and 4.5 MB for the two alternatives it compares itself with. The architecture decision also reaches into the source build. `ARCH` defaults to arm64 in the Makefile, and `make build ARCH=x86_64` cross-builds the Intel app through the generic destination because an x86_64 architecture destination is not valid on Apple Silicon, so that result runs locally under Rosetta rather than natively:
ifeq ($(ARCH),arm64)
DEST := platform=macOS,arch=arm64
ARCHFLAGS := ARCHS=arm64
else
DEST := generic/platform=macOS
ARCHFLAGS := ARCHS=$(ARCH) ONLY_ACTIVE_ARCH=NO
endifWhichever route you take, updates arrive through Sparkle on stable and beta channels with a custom update window. Two of the recent tags are v1.7.1-beta.1 and v1.7.1-beta.2, both carrying the same 1.7.1 number, so a build tells you it is a beta but not which beta unless you read the tag.
No Xcode project in the tree, so the build starts with xcodegen
The top level holds `.github/`, `.gitignore`, AGENTS.md, CLAUDE.md, LICENSE, Makefile, README.md, SECURITY.md, Sources/, Tests/, docs/, project.yml and scripts/. There is no `.xcodeproj` among them. The specification lives in project.yml, and every build path regenerates the project before touching xcodebuild:
gen:
xcodegen generate`build`, `test` and `run` all depend on `gen`, so a first checkout cannot be compiled until xcodegen is installed, and a stale generated project is rebuilt on every invocation rather than diffed. The same file also declares nine phony targets in one block, including archive, dmg, clean and kill, plus five update-test targets: update-test-none, update-test-download-fail, update-test-extract-fail, update-test-success and update-test-stop. Those five names line up with the failure modes an updater actually has to survive, and they sit next to a comment on test-hw warning that running the hardware suites briefly switches the real input source. Two instruction files aimed at coding agents, AGENTS.md and CLAUDE.md, are committed at the top level alongside SECURITY.md.
A Debug build installs under a different name, and the dmg path never changes
`CONFIG` defaults to Debug, and the app name follows it:
ifeq ($(CONFIG),Debug)
APP_NAME := LockIME Dev
else
APP_NAME := LockIME
endifA development build therefore installs as a second, separately named application rather than replacing the one from Homebrew, which is the arrangement you want while trying rules, and it means both copies can sit in Applications at once with their own settings. The same conditional does not reach the disk image variable, which is fixed at build/dmg/LockIME.dmg regardless of configuration, so nothing in the visible variable block distinguishes a development package from a release one. The launch target respects the name difference and matches on it exactly: it kills the process by exact name, tolerates nothing already running, then polls for up to fifty attempts at a tenth of a second before opening the app. So `make run` will not disturb a release copy of LockIME while it starts the dev one, and it will give up waiting after about five seconds rather than block.
A program that keeps re-asserting its own source makes LockIME stand down and log it
The conflict-aware behavior is the most useful thing in the feature list and the least discussed. When another program repeatedly pushes its own input source back, LockIME notices the tug-of-war and briefly stands down instead of flip-flopping forever, then writes Conflict detected into the activation log. The lasting fix is yours to configure: such a program gets its own Ignore rule or its own Switch to rule, and until you add one, the other program wins every time. That is a deliberate trade. Re-locking is the product, so the failure mode of re-locking is fought by whoever else is holding the input source, and the app resolves it by yielding and writing a log line rather than by holding position. The 24-hour activation log is what makes the yield visible, and it is also the place to look when you want to know what was switched, why, and for how long.
Three global behaviors, and the difference between Switch and None
The global default input source gets one of three behaviors. Lock pins it everywhere and re-applies it whenever it drifts. Switch moves you into it once whenever an app falls back to the global default, either because the app has no rule or because its rule is set to Use default, and then leaves you alone. None does nothing globally, leaving the app to act only through your per-app and per-URL rules. The same lock-or-switch split exists per rule: a per-app or per-URL rule can lock a source and re-apply it whenever it drifts, or it can switch once at focus time and step out of the way. That second mode is what keeps the app from feeling like a fight, and it is the mode the two alternatives compared against do not offer per rule at all, which the comparison table records as a row they both fail. A per-app keyboard shortcut completes the set by cycling or unbinding the rule for whichever app is frontmost, so a bad rule can be removed without opening settings at all.
URL rules are four match types in a list you reorder by dragging
Per-URL rules belong to the optional enhanced mode and match in four ways: a domain and its subdomains, an exact domain, a domain keyword, or a regular expression over the full URL. They apply in a priority order you arrange by dragging, and the first match wins. There is no specificity logic underneath that, which means the order is the entire policy: a keyword rule sitting above a regular expression will swallow every URL the expression would have caught, and the fix is to move rows rather than to rewrite patterns. The address bar gets its own rule type as well, with its own lock, switch and priority behavior. A `lockime://` URL scheme is supposed to let other apps, scripts and Shortcuts drive all of this, and the feature list points further down the page for how; the document ends one line into an unfinished changelog heading, so the parameter names, the callback keys and the accepted actions are not written down anywhere a reader can act on, and the automation interface has to be discovered from the app itself.
Bare-process rules key on the executable name, so every java shares one rule
Rules are not limited to application bundles. A program that runs from a bare executable can get a rule too, and while it is running the app picker lists it under a synthetic identity written as process: followed by the executable name. The worked example in the documentation is the java binary that Minecraft runs, spawned by a third-party launcher, which shows up as process:java. The identity is the executable name and nothing else, so every process started from an executable of that name lands under the same rule regardless of who launched it or what it is for. On a machine with several Java tools, or with a launcher and a server both named java, one rule covers all of them and the fine-grained alternative does not exist. This is the cheapest way to bind a launcher-driven process, and it is also the rule you will end up narrowing by hand later.
Core locking needs no permission, and a dash in the table means undocumented
The permission story splits in two. Core locking asks for no system permissions at all, and the optional enhanced mode is gated behind Accessibility, which is what unlocks the finer-grained per-URL rules and the focused-field rules. The comparison table is unusually careful about what its own cells mean. Its footnote says a dash marks an undocumented capability rather than a confirmed absence, and that the closed-source alternative it lists does not document its permission requirements at all, while reading a browser address bar for per-website rules needs Accessibility in practice. The same honesty shows in the rows where LockIME is the one without a feature: on-screen input hints are marked absent for it and present for both competitors. The remaining columns put a macOS 14 floor against 11 and 10.15, nine interface languages with live switching against six and three, and a final row that ticks maintenance in 2026 for all three projects without attaching a date to the other two.
Editorial conclusion
Worth the setup if you fight input-source drift on a daily basis and actually use the per-site rules, since the lock-versus-switch distinction is what the two open-source alternatives do not give you. Before switching, confirm three things: how many games or tools will need their own Ignore rule after the first Conflict detected entry, whether any two programs you run share an executable name, and whether the Intel disk image you grab is the native build or the generic one. You also inherit a macOS 14 floor and a manual architecture choice on every fresh download.
Frequently asked questions
Does LockIME need Accessibility permission to lock my input source?
Not for the core behavior. The README states that core locking requires no system permissions, and an optional Accessibility-gated enhanced mode is what unlocks the finer-grained per-URL and focused-field rules.
How are per-URL rules matched and prioritized in LockIME?
Four match types are offered: a domain and its subdomains, an exact domain, a domain keyword, and a regular expression over the full URL. They apply in a priority order you arrange by dragging, and the first match wins.
Which Macs and macOS versions does LockIME support?
macOS 14 and later, on both Apple silicon and Intel, but as separate apps: you download the `-arm64` or the `-x86_64` disk image, and the Homebrew cask picks the one matching your architecture.
Can LockIME apply a rule to a program that runs without an app bundle?
Yes. Programs running from a bare executable get a synthetic identity of process: followed by the executable name in the app picker, so a bare `java` process spawned by a third-party launcher appears as `process:java`.
What does a dash mean in the LockIME comparison table?
It marks an undocumented capability rather than a confirmed absence, as the footnote under the table says, and the same footnote adds that the closed-source project it lists does not document its permission requirements at all.
How does LockIME update itself, and how do its beta builds differ?
Through Sparkle, on stable and beta channels with a custom update window. Recent tags include v1.7.1 alongside v1.7.1-beta.1 and v1.7.1-beta.2, so two beta builds share the 1.7.1 number and are told apart only by the tag.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/oomol-lab-lockime)Star history chart for your README
[](https://hysenlabs.com/projects/oomol-lab-lockime)Was this useful?