Skip to content

Commit 84ffc96

Browse files
authored
feat!: collapse the two-level enable model into one master switch (#50)
Signed-off-by: Kevin Cui <bh@bugs.cc>
1 parent 1cd8c39 commit 84ffc96

35 files changed

Lines changed: 310 additions & 429 deletions

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ Either way, the app keeps itself up to date via Sparkle.
6161
- **Lock or switch** — per-app and per-URL rules can *lock* an input source
6262
(re-applied whenever it drifts) or just *switch* to it once when you focus the
6363
app or page, then step out of the way and let you change it freely.
64-
- **Lock globally, or just switch**one **Enable LockIME** switch powers
65-
everything; a subordinate **Enable locking** toggle controls only the
66-
continuous lock. Turn locking off to use LockIME as a pure per-app/per-site
67-
switcher — it switches you in, then leaves you free, pinning nothing.
64+
- **Lock globally, or just switch**set the global default to one input
65+
source to pin it everywhere, or set it to **None** to use LockIME as a pure
66+
per-app/per-site switcher — it switches you in, then leaves you free, pinning
67+
nothing.
6868
- **Flexible URL matching** — per-URL rules (enhanced mode) match by a domain and
6969
its subdomains, an exact domain, a domain keyword, or a regular expression over
7070
the full URL, and apply in a priority order you drag to arrange — first match
@@ -147,7 +147,7 @@ a GUI switcher).
147147
## Automation
148148

149149
LockIME exposes a `lockime://` URL scheme so other apps, scripts, Shortcuts, and
150-
launchers can drive it — toggle locking, retarget the input source, manage
150+
launchers can drive it — turn it on or off, retarget the input source, manage
151151
rules, and read state back with [x-callback-url](https://x-callback-url.com)
152152
callbacks. It is off by default — turn it on in **Settings ▸ General ▸
153153
Automation**.

Sources/LockIME/API/URLCommandHandler.swift

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,13 @@ final class URLCommandHandler {
4949
/// `[[String: Any]]` array) for query commands, `nil` for actions.
5050
private func perform(_ command: URLCommand) -> Result<Any?, URLCommandError> {
5151
switch command {
52-
// Master ("Enable LockIME") — gates the whole app.
52+
// The single on/off ("Enable LockIME") — gates the whole app.
5353
case .lock:
5454
state.setMasterEnabled(true); return .success(nil)
5555
case .unlock:
5656
state.setMasterEnabled(false); return .success(nil)
5757
case .toggleLock:
5858
state.setMasterEnabled(!state.isAppEnabled); return .success(nil)
59-
// Lock sub-toggle ("Enable locking").
60-
case .setLocking(let flag):
61-
state.setLockingEnabled(resolve(flag, current: state.config.lockingEnabled)); return .success(nil)
6259

6360
// Global source targeting
6461
case .lockToSource(let selector):
@@ -243,11 +240,8 @@ final class URLCommandHandler {
243240

244241
private func statusPayload() -> [String: Any] {
245242
var payload: [String: Any] = [
246-
// `enabled` = the master ("Enable LockIME"); `lockingEnabled` = the
247-
// lock sub-toggle; `locked` = both (a continuous lock is in force).
243+
// `enabled` = the single "Enable LockIME" switch.
248244
"enabled": state.isAppEnabled,
249-
"lockingEnabled": state.config.lockingEnabled,
250-
"locked": state.isLocked,
251245
"enhancedMode": state.config.enhancedModeEnabled,
252246
"launchAtLogin": state.launchAtLoginActive,
253247
"accessibilityGranted": state.accessibilityGranted,

Sources/LockIME/AppState.swift

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -101,20 +101,12 @@ final class AppState {
101101
/// pane can route the user to General's single Accessibility grant.
102102
var settingsTab: SettingsTab = .general
103103

104-
/// The master on/off — "Enable LockIME". Mirrors `config.isEnabled`; gates
105-
/// the whole app (both locking and switching). Bound by the General master
106-
/// toggle.
104+
/// The single on/off — "Enable LockIME". Mirrors `config.isEnabled`; gates
105+
/// the whole app (both locking and switching). Bound by the General toggle,
106+
/// and what every state surface reflects (tray/About icon, menu glyph +
107+
/// "Enabled/Disabled" header, the locked-source checkmark).
107108
var isAppEnabled: Bool { config.isEnabled }
108109

109-
/// Whether a **continuous lock** is in force right now: the master is on *and*
110-
/// the lock sub-toggle is on. This is what the padlock surfaces represent
111-
/// (tray/About icon, menu glyph + "Locked/Unlocked" header, the locked-source
112-
/// checkmark) — they speak to the *lock* capability, not mere app activeness.
113-
/// For anyone who leaves locking on (the default) this equals `isEnabled`, so
114-
/// those surfaces look exactly as before; only the opt-in pure-switch mode
115-
/// (master on, locking off) shows them "unlocked".
116-
var isLocked: Bool { config.isEnabled && config.lockingEnabled }
117-
118110
/// The SwiftData container backing the activation log (for `.modelContainer`).
119111
var modelContainer: ModelContainer { logStore.container }
120112

@@ -229,17 +221,15 @@ final class AppState {
229221
updateController.onCheckOutcome = { [weak self] outcome in self?.presentUpdateOutcome(outcome) }
230222
updateController.start()
231223

232-
// Global toggle-lock shortcut: flips the master ("Enable LockIME") on/off,
233-
// the app's quick stop/start. `lockingEnabled` persists across toggles, so
234-
// a pure-switch user (locking off) toggling the app off then on stays in
235-
// pure-switch mode rather than silently re-engaging the lock.
224+
// Global toggle-lock shortcut: flips "Enable LockIME" on/off — the
225+
// app's quick stop/start.
236226
KeyboardShortcuts.onKeyUp(for: .toggleLock) { [weak self] in
237227
guard let self else { return }
238228
self.setMasterEnabled(!self.isAppEnabled)
239229
}
240230

241231
// Global "lock to previous/next source" — cycle the global target
242-
// through the input-source list (wrapping), turning locking on.
232+
// through the input-source list (wrapping), turning LockIME on.
243233
KeyboardShortcuts.onKeyUp(for: .globalPreviousSource) { [weak self] in
244234
self?.cycleGlobalSource(.previous)
245235
}
@@ -343,39 +333,26 @@ final class AppState {
343333
commit(reason: .lockEngaged)
344334
}
345335

346-
/// Toggle the **continuous-lock** capability (the General "Enable locking"
347-
/// sub-toggle), subordinate to the master. Turning it off drops every standing
348-
/// lock — the global default, per-app `.locked` rules, URL `.lock` rules, and
349-
/// the address-bar lock all go inert — while one-shot switch rules keep firing.
350-
/// That is the "act like Input Source Pro" mode: per-context auto-switch with
351-
/// no global lock. No effect while the master is off (the app is fully idle).
352-
func setLockingEnabled(_ on: Bool) {
353-
config.lockingEnabled = on
354-
commit(reason: .lockEngaged)
355-
}
356-
357336
func setDefaultSource(_ id: InputSourceID?) {
358337
config.defaultSourceID = id
359338
commit()
360339
}
361340

362341
/// Lock to a specific source from the menu bar: make it the global target and
363-
/// engage the lock in a single commit — turning **both** the master and the
364-
/// lock sub-toggle on, so a one-tap menu pick always pins, even from a
365-
/// pure-switch or fully-off state. Clicking the already-locked source instead
366-
/// clears the global target via `setDefaultSource(nil)` (leaving the app and
342+
/// turn LockIME on in a single commit, so a one-tap menu pick always pins,
343+
/// even from an off state. Clicking the already-locked source instead clears
344+
/// the global target via `setDefaultSource(nil)` (leaving the app and
367345
/// switching alive).
368346
func lockToSource(_ id: InputSourceID) {
369347
config.defaultSourceID = id
370348
config.isEnabled = true
371-
config.lockingEnabled = true
372349
commit(reason: .lockEngaged)
373350
}
374351

375352
// MARK: - Shortcut-driven source cycling
376353

377354
/// Lock the *global* target to the previous/next input source in the list,
378-
/// wrapping around the ends, and turn locking on — the same write path as
355+
/// wrapping around the ends, and turn LockIME on — the same write path as
379356
/// `lockToSource`. Never lands on "none", and does nothing when fewer than
380357
/// two input sources are installed (there's nowhere to cycle).
381358
func cycleGlobalSource(_ direction: CycleDirection) {
@@ -385,7 +362,6 @@ final class AppState {
385362
) else { return }
386363
config.defaultSourceID = next
387364
config.isEnabled = true
388-
config.lockingEnabled = true
389365
commit(reason: .lockEngaged)
390366
}
391367

0 commit comments

Comments
 (0)