From c1ed57425efa4feb2da974a3ad1a8128025a838f Mon Sep 17 00:00:00 2001 From: Kevin Cui Date: Sat, 27 Jun 2026 10:16:09 -0400 Subject: [PATCH 1/2] feat: separate the app master switch from locking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single "Enable input-source locking" toggle gated everything — both the continuous lock and the one-shot switch rules. Turning it off to avoid a global lock silently disabled per-app and per-URL switch rules too, so LockIME could not be used as a pure switcher (the Input Source Pro model the issue asks for). Split the model in two: the master `isEnabled` ("Enable LockIME") gates the whole app, and a new subordinate `lockingEnabled` ("Enable locking", on by default) gates only continuous locking. One-shot switching is now gated by the master alone, so master on plus locking off pins nothing while per-app/per-site switch rules keep firing. `LockConfiguration` decodes an absent `lockingEnabled` as `true`, so existing configs and the lock-on default are unchanged. The menu bar, the toggle-lock shortcut, the `lockime://` API (new `set-locking` command; `status` now reports `enabled` and `lockingEnabled`), and the README / URL-Scheme-API docs in all nine languages are updated to match. Closes #37 Signed-off-by: Kevin Cui --- README.md | 10 ++- Sources/LockIME/API/URLCommandHandler.swift | 11 ++- Sources/LockIME/AppState.swift | 44 ++++++++-- Sources/LockIME/Localizable.xcstrings | 88 +++++++++++++++---- Sources/LockIME/UI/MenuBarView.swift | 13 +-- .../UI/Settings/GeneralSettingsPane.swift | 19 +++- Sources/LockIMEKit/API/URLCommand.swift | 8 +- Sources/LockIMEKit/Backup/ImportPlan.swift | 5 +- .../LockIMEKit/LockEngine/LockEngine.swift | 26 ++++-- .../LockIMEKit/Rules/LockConfiguration.swift | 19 +++- .../LockConfigurationTests.swift | 18 ++++ Tests/LockIMEKitTests/LockEngineTests.swift | 64 ++++++++++++++ .../URLCommandParserTests.swift | 12 +++ docs/DESIGN.md | 24 +++-- docs/README/README.de.md | 3 +- docs/README/README.es.md | 3 +- docs/README/README.fr.md | 3 +- docs/README/README.ja.md | 3 +- docs/README/README.pt.md | 3 +- docs/README/README.ru.md | 3 +- docs/README/README.zh-CN.md | 3 +- docs/README/README.zh-TW.md | 3 +- docs/URL-Scheme-API/README.de.md | 21 ++++- docs/URL-Scheme-API/README.es.md | 19 +++- docs/URL-Scheme-API/README.fr.md | 14 ++- docs/URL-Scheme-API/README.ja.md | 19 +++- docs/URL-Scheme-API/README.md | 19 +++- docs/URL-Scheme-API/README.pt.md | 20 ++++- docs/URL-Scheme-API/README.ru.md | 14 ++- docs/URL-Scheme-API/README.zh-CN.md | 14 ++- docs/URL-Scheme-API/README.zh-TW.md | 14 ++- 31 files changed, 435 insertions(+), 104 deletions(-) diff --git a/README.md b/README.md index 8165268..f837f86 100644 --- a/README.md +++ b/README.md @@ -61,15 +61,19 @@ Either way, the app keeps itself up to date via Sparkle. - **Lock or switch** — per-app and per-URL rules can *lock* an input source (re-applied whenever it drifts) or just *switch* to it once when you focus the app or page, then step out of the way and let you change it freely. +- **Lock globally, or just switch** — one **Enable LockIME** switch powers + everything; a subordinate **Enable locking** toggle controls only the + continuous lock. Turn locking off to use LockIME as a pure per-app/per-site + switcher — it switches you in, then leaves you free, pinning nothing. - **Flexible URL matching** — per-URL rules (enhanced mode) match by a domain and its subdomains, an exact domain, a domain keyword, or a regular expression over the full URL, and apply in a priority order you drag to arrange — first match wins. - **Menu-bar control** — activate/deactivate, switch the locked input source, view the current source, and track the activation count from the menu bar. -- **Keyboard shortcuts** — configurable global shortcuts to toggle locking and - cycle the locked input source, plus per-app shortcuts to cycle or unbind the - rule for whichever app is frontmost. +- **Keyboard shortcuts** — configurable global shortcuts to toggle LockIME on or + off and cycle the locked input source, plus per-app shortcuts to cycle or + unbind the rule for whichever app is frontmost. - **Launch at login** — starts automatically when you log in (off by default). - **Light & dark mode** — a unified, system-native design language that adapts to light and dark appearance, plus a bespoke app icon. See diff --git a/Sources/LockIME/API/URLCommandHandler.swift b/Sources/LockIME/API/URLCommandHandler.swift index 564b555..60a9a96 100644 --- a/Sources/LockIME/API/URLCommandHandler.swift +++ b/Sources/LockIME/API/URLCommandHandler.swift @@ -49,13 +49,16 @@ final class URLCommandHandler { /// `[[String: Any]]` array) for query commands, `nil` for actions. private func perform(_ command: URLCommand) -> Result { switch command { - // Master lock + // Master ("Enable LockIME") — gates the whole app. case .lock: state.setMasterEnabled(true); return .success(nil) case .unlock: state.setMasterEnabled(false); return .success(nil) case .toggleLock: - state.setMasterEnabled(!state.isLocked); return .success(nil) + state.setMasterEnabled(!state.isAppEnabled); return .success(nil) + // Lock sub-toggle ("Enable locking"). + case .setLocking(let flag): + state.setLockingEnabled(resolve(flag, current: state.config.lockingEnabled)); return .success(nil) // Global source targeting case .lockToSource(let selector): @@ -238,6 +241,10 @@ final class URLCommandHandler { private func statusPayload() -> [String: Any] { var payload: [String: Any] = [ + // `enabled` = the master ("Enable LockIME"); `lockingEnabled` = the + // lock sub-toggle; `locked` = both (a continuous lock is in force). + "enabled": state.isAppEnabled, + "lockingEnabled": state.config.lockingEnabled, "locked": state.isLocked, "enhancedMode": state.config.enhancedModeEnabled, "launchAtLogin": state.launchAtLoginActive, diff --git a/Sources/LockIME/AppState.swift b/Sources/LockIME/AppState.swift index 101c589..8eba558 100644 --- a/Sources/LockIME/AppState.swift +++ b/Sources/LockIME/AppState.swift @@ -89,8 +89,19 @@ final class AppState { /// pane can route the user to General's single Accessibility grant. var settingsTab: SettingsTab = .general - /// Master on/off, mirroring `config.isEnabled`. - var isLocked: Bool { config.isEnabled } + /// The master on/off — "Enable LockIME". Mirrors `config.isEnabled`; gates + /// the whole app (both locking and switching). Bound by the General master + /// toggle. + var isAppEnabled: Bool { config.isEnabled } + + /// Whether a **continuous lock** is in force right now: the master is on *and* + /// the lock sub-toggle is on. This is what the padlock surfaces represent + /// (tray/About icon, menu glyph + "Locked/Unlocked" header, the locked-source + /// checkmark) — they speak to the *lock* capability, not mere app activeness. + /// For anyone who leaves locking on (the default) this equals `isEnabled`, so + /// those surfaces look exactly as before; only the opt-in pure-switch mode + /// (master on, locking off) shows them "unlocked". + var isLocked: Bool { config.isEnabled && config.lockingEnabled } /// The SwiftData container backing the activation log (for `.modelContainer`). var modelContainer: ModelContainer { logStore.container } @@ -188,10 +199,13 @@ final class AppState { updateController.onCheckOutcome = { [weak self] outcome in self?.presentUpdateOutcome(outcome) } updateController.start() - // Global toggle-lock shortcut. + // Global toggle-lock shortcut: flips the master ("Enable LockIME") on/off, + // the app's quick stop/start. `lockingEnabled` persists across toggles, so + // a pure-switch user (locking off) toggling the app off then on stays in + // pure-switch mode rather than silently re-engaging the lock. KeyboardShortcuts.onKeyUp(for: .toggleLock) { [weak self] in guard let self else { return } - self.setMasterEnabled(!self.isLocked) + self.setMasterEnabled(!self.isAppEnabled) } // Global "lock to previous/next source" — cycle the global target @@ -278,17 +292,32 @@ final class AppState { commit(reason: .lockEngaged) } + /// Toggle the **continuous-lock** capability (the General "Enable locking" + /// sub-toggle), subordinate to the master. Turning it off drops every standing + /// lock — the global default, per-app `.locked` rules, URL `.lock` rules, and + /// the address-bar lock all go inert — while one-shot switch rules keep firing. + /// That is the "act like Input Source Pro" mode: per-context auto-switch with + /// no global lock. No effect while the master is off (the app is fully idle). + func setLockingEnabled(_ on: Bool) { + config.lockingEnabled = on + commit(reason: .lockEngaged) + } + func setDefaultSource(_ id: InputSourceID?) { config.defaultSourceID = id commit() } - /// Lock to a specific source from the menu bar: make it the global target - /// and turn locking on in a single commit. Clicking the already-locked - /// source instead disables locking via `setMasterEnabled(false)`. + /// Lock to a specific source from the menu bar: make it the global target and + /// engage the lock in a single commit — turning **both** the master and the + /// lock sub-toggle on, so a one-tap menu pick always pins, even from a + /// pure-switch or fully-off state. Clicking the already-locked source instead + /// clears the global target via `setDefaultSource(nil)` (leaving the app and + /// switching alive). func lockToSource(_ id: InputSourceID) { config.defaultSourceID = id config.isEnabled = true + config.lockingEnabled = true commit(reason: .lockEngaged) } @@ -305,6 +334,7 @@ final class AppState { ) else { return } config.defaultSourceID = next config.isEnabled = true + config.lockingEnabled = true commit(reason: .lockEngaged) } diff --git a/Sources/LockIME/Localizable.xcstrings b/Sources/LockIME/Localizable.xcstrings index 2f754c3..a373e87 100644 --- a/Sources/LockIME/Localizable.xcstrings +++ b/Sources/LockIME/Localizable.xcstrings @@ -2858,54 +2858,106 @@ } } }, - "Enable input-source locking": { + "Enable LockIME": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "启用输入法锁定" + "value": "启用 LockIME" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "啟用輸入法鎖定" + "value": "啟用 LockIME" } }, "ja": { "stringUnit": { "state": "translated", - "value": "入力ソースのロックを有効にする" + "value": "LockIME を有効にする" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Activer le verrouillage de la source de saisie" + "value": "Activer LockIME" } }, "de": { "stringUnit": { "state": "translated", - "value": "Eingabequellen-Sperre aktivieren" + "value": "LockIME aktivieren" } }, "es": { "stringUnit": { "state": "translated", - "value": "Activar el bloqueo de la fuente de entrada" + "value": "Activar LockIME" } }, "pt": { "stringUnit": { "state": "translated", - "value": "Ativar o bloqueio da fonte de entrada" + "value": "Ativar o LockIME" } }, "ru": { "stringUnit": { "state": "translated", - "value": "Включить блокировку источника ввода" + "value": "Включить LockIME" + } + } + } + }, + "Enable locking": { + "localizations": { + "zh-Hans": { + "stringUnit": { + "state": "translated", + "value": "启用锁定" + } + }, + "zh-Hant": { + "stringUnit": { + "state": "translated", + "value": "啟用鎖定" + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "ロックを有効にする" + } + }, + "fr": { + "stringUnit": { + "state": "translated", + "value": "Activer le verrouillage" + } + }, + "de": { + "stringUnit": { + "state": "translated", + "value": "Sperre aktivieren" + } + }, + "es": { + "stringUnit": { + "state": "translated", + "value": "Activar el bloqueo" + } + }, + "pt": { + "stringUnit": { + "state": "translated", + "value": "Ativar o bloqueio" + } + }, + "ru": { + "stringUnit": { + "state": "translated", + "value": "Включить блокировку" } } } @@ -4730,54 +4782,54 @@ } } }, - "When enabled, LockIME keeps the keyboard input source pinned to the configured target.": { + "Enable LockIME to apply your rules. With locking on, it keeps the input source pinned to your target; turn locking off to only switch on entry — for the apps and sites you set — and stay free to change it.": { "localizations": { "zh-Hans": { "stringUnit": { "state": "translated", - "value": "启用后,LockIME 会将键盘输入法固定为所配置的目标。" + "value": "启用 LockIME 后规则才会生效。开启锁定时,它会将输入法持续固定到你的目标;关闭锁定则只在进入你设定的应用和网站时切换一次,之后你可自由更改。" } }, "zh-Hant": { "stringUnit": { "state": "translated", - "value": "啟用後,LockIME 會將鍵盤輸入法固定為所設定的目標。" + "value": "啟用 LockIME 後規則才會生效。開啟鎖定時,它會將輸入法持續固定到你的目標;關閉鎖定則只在進入你設定的應用程式和網站時切換一次,之後你可自由變更。" } }, "ja": { "stringUnit": { "state": "translated", - "value": "有効にすると、LockIME はキーボードの入力ソースを設定したターゲットに固定し続けます。" + "value": "ルールを適用するには LockIME を有効にします。ロックをオンにすると入力ソースを指定したターゲットに固定し続けます。ロックをオフにすると、設定したアプリやサイトに入ったときに一度だけ切り替え、その後は自由に変更できます。" } }, "fr": { "stringUnit": { "state": "translated", - "value": "Une fois activé, LockIME maintient la source de saisie du clavier sur la cible configurée." + "value": "Activez LockIME pour appliquer vos règles. Lorsque le verrouillage est activé, il maintient la source de saisie sur votre cible ; désactivez-le pour ne basculer qu'à l'entrée — pour les apps et sites que vous définissez — tout en restant libre d'en changer." } }, "de": { "stringUnit": { "state": "translated", - "value": "Wenn aktiviert, hält LockIME die Tastatur-Eingabequelle auf dem konfigurierten Ziel." + "value": "Aktivieren Sie LockIME, um Ihre Regeln anzuwenden. Bei aktivierter Sperre bleibt die Eingabequelle auf Ihrem Ziel fixiert; schalten Sie die Sperre aus, um nur beim Wechsel zu den von Ihnen festgelegten Apps und Websites einmal umzuschalten – und sie weiterhin frei zu ändern." } }, "es": { "stringUnit": { "state": "translated", - "value": "Cuando está activado, LockIME mantiene la fuente de entrada del teclado fijada en el destino configurado." + "value": "Activa LockIME para aplicar tus reglas. Con el bloqueo activado, mantiene la fuente de entrada fijada en tu destino; desactívalo para cambiar solo al entrar —en las apps y sitios que definas— y seguir pudiendo cambiarla libremente." } }, "pt": { "stringUnit": { "state": "translated", - "value": "Quando ativado, o LockIME mantém a fonte de entrada do teclado fixada no destino configurado." + "value": "Ative o LockIME para aplicar suas regras. Com o bloqueio ativado, ele mantém a fonte de entrada fixada no seu destino; desative o bloqueio para alternar apenas ao entrar — nos apps e sites que você definir — e continuar livre para alterá-la." } }, "ru": { "stringUnit": { "state": "translated", - "value": "Когда включено, LockIME удерживает источник ввода клавиатуры на заданной цели." + "value": "Включите LockIME, чтобы применить правила. Когда блокировка включена, источник ввода остаётся закреплён за вашей целью; выключите её, чтобы переключаться только при входе — для заданных вами приложений и сайтов — и сохранять свободу смены." } } } diff --git a/Sources/LockIME/UI/MenuBarView.swift b/Sources/LockIME/UI/MenuBarView.swift index 201a4ff..d9e2261 100644 --- a/Sources/LockIME/UI/MenuBarView.swift +++ b/Sources/LockIME/UI/MenuBarView.swift @@ -46,16 +46,19 @@ struct MenuBarView: View { // lock toggles. (A `Toggle`'s native checkmark lives in NSMenu's *state* // column, which collapses to zero width when nothing is checked — that // is what made the menu jump.) Clicking an unchecked source locks to it - // (sets target + enables, one commit); clicking the checked source - // disables locking. No separate master toggle, no submenu. Source names + // (sets the global target + engages master and locking, one commit); + // clicking the checked source clears the global target (app and switch + // rules stay live). No separate master toggle, no submenu. Source names // are verbatim system strings, not catalog keys. The global toggle-lock - // shortcut (Settings ▸ Shortcuts) is unchanged: it flips locking on/off - // against the remembered target. + // shortcut (Settings ▸ Shortcuts) flips the master (Enable LockIME) on/off. ForEach(state.availableSources) { source in let isLockedTo = state.isLocked && state.config.defaultSourceID == source.id Button { if isLockedTo { - state.setMasterEnabled(false) + // Clear just the global lock target — the app and any one-shot + // switch rules stay alive. (Use Settings to turn LockIME off + // entirely, or to toggle locking without losing the target.) + state.setDefaultSource(nil) } else { state.lockToSource(source.id) } diff --git a/Sources/LockIME/UI/Settings/GeneralSettingsPane.swift b/Sources/LockIME/UI/Settings/GeneralSettingsPane.swift index 527022b..53589a6 100644 --- a/Sources/LockIME/UI/Settings/GeneralSettingsPane.swift +++ b/Sources/LockIME/UI/Settings/GeneralSettingsPane.swift @@ -6,22 +6,33 @@ struct GeneralSettingsPane: View { @Environment(AppState.self) private var state var body: some View { - let lockBinding = Binding( - get: { state.isLocked }, + let masterBinding = Binding( + get: { state.isAppEnabled }, set: { newValue in withAnimation(DS.Motion.toggle) { state.setMasterEnabled(newValue) } } ) + let lockingBinding = Binding( + get: { state.config.lockingEnabled }, + set: { newValue in + withAnimation(DS.Motion.toggle) { state.setLockingEnabled(newValue) } + } + ) Form { Section { - Toggle("Enable input-source locking", isOn: lockBinding) + Toggle("Enable LockIME", isOn: masterBinding) + // Subordinate to the master (HIG dependency): dimmed and inert + // while LockIME is off. Turning it off is the "act like Input + // Source Pro" mode — switch rules still fire, nothing is pinned. + Toggle("Enable locking", isOn: lockingBinding) + .disabled(!state.isAppEnabled) LabeledContent("Current source", value: state.currentSourceName) LabeledContent("Activations", value: state.activationCount.formatted()) } header: { Text("Status") } footer: { - SectionFooter("When enabled, LockIME keeps the keyboard input source pinned to the configured target.") + SectionFooter("Enable LockIME to apply your rules. With locking on, it keeps the input source pinned to your target; turn locking off to only switch on entry — for the apps and sites you set — and stay free to change it.") } Section { diff --git a/Sources/LockIMEKit/API/URLCommand.swift b/Sources/LockIMEKit/API/URLCommand.swift index 0071513..a9a9ce9 100644 --- a/Sources/LockIMEKit/API/URLCommand.swift +++ b/Sources/LockIMEKit/API/URLCommand.swift @@ -32,11 +32,15 @@ public enum FlagArg: Equatable, Sendable { /// values present in the URL; whether a referenced source/rule/app actually /// exists is a runtime question answered by the executor, not the parser. public enum URLCommand: Equatable, Sendable { - // Master lock + // Master ("Enable LockIME") — gates the whole app (locking + switching). case lock case unlock case toggleLock + // Lock sub-toggle ("Enable locking") — subordinate to the master; off ⇒ no + // continuous lock, but one-shot switch rules keep firing (Input Source Pro mode). + case setLocking(FlagArg) + // Global source targeting case lockToSource(SourceSelector) case setDefaultSource(SourceSelector?) // nil clears the global default @@ -265,6 +269,8 @@ public enum URLCommandParser { return .success(.unlock) case "toggle-lock", "toggle": return .success(.toggleLock) + case "set-locking", "locking": + return flag(params, "enabled").map { .setLocking($0) } case "lock-to-source": return requiredSource(params).map { .lockToSource($0) } diff --git a/Sources/LockIMEKit/Backup/ImportPlan.swift b/Sources/LockIMEKit/Backup/ImportPlan.swift index 501f684..fe4f0ae 100644 --- a/Sources/LockIMEKit/Backup/ImportPlan.swift +++ b/Sources/LockIMEKit/Backup/ImportPlan.swift @@ -353,8 +353,9 @@ public struct ImportPlan: Sendable, Equatable { // MARK: - Resolution /// Fold the plan into a final configuration. Pure: the per-device runtime - /// flags (`isEnabled`, `enhancedModeEnabled`) are carried straight from the - /// base config and never imported. + /// flags (`isEnabled`, `lockingEnabled`, `enhancedModeEnabled`) are carried + /// straight from the base config (via `var result = baseConfig` below) and + /// never imported. public func resolvedConfiguration() -> LockConfiguration { var appRules: [String: AppRule] var defaultSource: InputSourceID? diff --git a/Sources/LockIMEKit/LockEngine/LockEngine.swift b/Sources/LockIMEKit/LockEngine/LockEngine.swift index 817a48f..f23e27e 100644 --- a/Sources/LockIMEKit/LockEngine/LockEngine.swift +++ b/Sources/LockIMEKit/LockEngine/LockEngine.swift @@ -131,17 +131,25 @@ public final class LockEngine { /// since whichever fires is the one that emits the activation event. public func apply(_ config: LockConfiguration, reason: ActivationReason = .configChanged) { self.config = config - // Order matters so disabling is side-effect free. When enabling (or - // re-applying while on), set the target first, then enforce. When - // disabling, stop enforcing *first* — otherwise reevaluate could force - // one last switch under the still-enabled state before the lock turns - // off (e.g. the source has drifted off target and the revert is pending). - if config.isEnabled { - reevaluate(reason: reason) // set target - controller.setEnabled(true, reason: reason) // then enforce on enable + // Continuous locking is *subordinate* to the master: it engages only when + // both "Enable LockIME" (`isEnabled`) and the lock sub-toggle + // (`lockingEnabled`) are on. One-shot switching is gated by the master + // alone (in `fireSwitchOnceIfNeeded`), so a master-on / locking-off config + // still fires switch rules while pinning nothing — the "act like Input + // Source Pro" mode. `reevaluate` always runs so the switch arm can fire + // whenever the master is on, regardless of the lock sub-toggle. + let lockEngaged = config.isEnabled && config.lockingEnabled + // Order matters so disengaging the lock is side-effect free. When + // engaging (or re-applying while engaged), set the target first, then + // enforce. Otherwise stop enforcing *first* — else reevaluate could force + // one last switch under the still-engaged lock before it turns off (e.g. + // the source has drifted off target and the revert is pending). + if lockEngaged { + reevaluate(reason: reason) // set lock target + fire switch + controller.setEnabled(true, reason: reason) // then enforce on engage } else { controller.setEnabled(false, reason: reason) // stop enforcing first - reevaluate(reason: reason) // update cached target only + reevaluate(reason: reason) // fire switch (if master on); cache lock target only } updateURLPolling() updateAddressBarMonitoring() diff --git a/Sources/LockIMEKit/Rules/LockConfiguration.swift b/Sources/LockIMEKit/Rules/LockConfiguration.swift index 9b6b8e7..917403f 100644 --- a/Sources/LockIMEKit/Rules/LockConfiguration.swift +++ b/Sources/LockIMEKit/Rules/LockConfiguration.swift @@ -161,8 +161,20 @@ public struct URLRule: Codable, Sendable, Hashable, Identifiable { /// The full persisted locking configuration. public struct LockConfiguration: Codable, Sendable, Equatable { - /// Master on/off (the tray "activate" toggle). + /// Master on/off — "Enable LockIME". When off, the app is fully idle: it + /// neither **locks** nor **switches**, regardless of any rule. This is the + /// single power switch the menu bar and the global toggle shortcut flip. public var isEnabled: Bool + /// Whether the **continuous-lock** capability is engaged, *subordinate* to + /// `isEnabled` (it only matters while the master is on). When off, no rule + /// ever pins a source continuously — the global default, per-app `.locked` + /// rules, URL `.lock` rules, and the address-bar lock all go inert — but the + /// **one-shot switch** capability is untouched and keeps firing (a `.switched` + /// app rule, a `.switchOnce` URL/address-bar rule). That is the "act like + /// Input Source Pro" mode: per-context auto-switch with no global lock. + /// Defaults **on** so an upgrade preserves the prior behavior (master on ⇒ + /// locking on), and a missing key decodes to `true` for the same reason. + public var lockingEnabled: Bool /// Global default locked source, used when no app rule applies. public var defaultSourceID: InputSourceID? /// Per-app overrides. @@ -194,6 +206,7 @@ public struct LockConfiguration: Codable, Sendable, Equatable { public init( isEnabled: Bool = false, + lockingEnabled: Bool = true, defaultSourceID: InputSourceID? = nil, appRules: [AppRule] = [], enhancedModeEnabled: Bool = false, @@ -204,6 +217,7 @@ public struct LockConfiguration: Codable, Sendable, Equatable { addressBarOutranksURLRules: Bool = true ) { self.isEnabled = isEnabled + self.lockingEnabled = lockingEnabled self.defaultSourceID = defaultSourceID self.appRules = appRules self.enhancedModeEnabled = enhancedModeEnabled @@ -219,6 +233,9 @@ public struct LockConfiguration: Codable, Sendable, Equatable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) isEnabled = try container.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? false + // Absent (any config persisted before this field existed) ⇒ `true`, so an + // upgrade keeps the prior "master on ⇒ locking on" behavior unchanged. + lockingEnabled = try container.decodeIfPresent(Bool.self, forKey: .lockingEnabled) ?? true defaultSourceID = try container.decodeIfPresent(InputSourceID.self, forKey: .defaultSourceID) appRules = try container.decodeIfPresent([AppRule].self, forKey: .appRules) ?? [] enhancedModeEnabled = try container.decodeIfPresent(Bool.self, forKey: .enhancedModeEnabled) ?? false diff --git a/Tests/LockIMEKitTests/LockConfigurationTests.swift b/Tests/LockIMEKitTests/LockConfigurationTests.swift index 8ef9e11..639a5c8 100644 --- a/Tests/LockIMEKitTests/LockConfigurationTests.swift +++ b/Tests/LockIMEKitTests/LockConfigurationTests.swift @@ -65,12 +65,30 @@ struct LockConfigurationTests { let config = try JSONDecoder().decode(LockConfiguration.self, from: Data("{}".utf8)) #expect(config == LockConfiguration.default) #expect(config.isEnabled == false) + #expect(config.lockingEnabled == true) #expect(config.defaultSourceID == nil) #expect(config.appRules.isEmpty) #expect(config.enhancedModeEnabled == false) #expect(config.urlRules.isEmpty) } + @Test("a pre-existing config (no lockingEnabled key) migrates to locking on") + func lockingEnabledDefaultsTrueWhenAbsent() throws { + // Any configuration persisted before the field existed lacks the key, so + // an upgrade must keep the prior "master on ⇒ locking on" behavior. + let json = #"{"isEnabled": true, "defaultSourceID": "com.apple.keylayout.US"}"# + let config = try JSONDecoder().decode(LockConfiguration.self, from: Data(json.utf8)) + #expect(config.lockingEnabled == true) + } + + @Test("an explicit lockingEnabled=false is honored (pure-switch mode persists)") + func lockingEnabledFalseIsHonored() throws { + let json = #"{"isEnabled": true, "lockingEnabled": false}"# + let config = try JSONDecoder().decode(LockConfiguration.self, from: Data(json.utf8)) + #expect(config.isEnabled == true) + #expect(config.lockingEnabled == false) + } + @Test("decoding a partial object keeps present keys and defaults the rest") func decodesPartial() throws { let json = #"{"isEnabled": true, "defaultSourceID": "com.apple.keylayout.US"}"# diff --git a/Tests/LockIMEKitTests/LockEngineTests.swift b/Tests/LockIMEKitTests/LockEngineTests.swift index ca72647..1762a3a 100644 --- a/Tests/LockIMEKitTests/LockEngineTests.swift +++ b/Tests/LockIMEKitTests/LockEngineTests.swift @@ -647,6 +647,70 @@ struct LockEngineSwitchTests { #expect(provider.current == us) } + // MARK: - Lock sub-toggle (`lockingEnabled`) — decoupled from switching + + @Test("locking off: the global default does not pin (no global lock)") + func lockingOffGlobalDefaultInert() { + let (engine, provider, _, _) = makeEngine(current: abc, frontmost: "com.foo.App") + // Master on, lock sub-toggle off: a set global default must NOT engage. + engine.apply(LockConfiguration(isEnabled: true, lockingEnabled: false, defaultSourceID: us)) + #expect(provider.selectCalls.isEmpty) + #expect(provider.current == abc) // left free, not pinned to us + } + + @Test("locking off: a switch app rule still fires (Input Source Pro mode)") + func lockingOffStillSwitches() { + let (engine, provider, monitor, _) = makeEngine(current: us, frontmost: "com.foo.App") + engine.apply(LockConfiguration( + isEnabled: true, lockingEnabled: false, defaultSourceID: us, + appRules: [AppRule(bundleID: "com.apple.Terminal", mode: .switched, lockedSourceID: abc)] + )) + #expect(provider.current == us) // foo: nothing pinned (global default inert) + + monitor.activate("com.apple.Terminal") + #expect(provider.current == abc) // switched once despite locking off + #expect(provider.selectCalls == [abc]) + } + + @Test("locking off: a lock app rule is inert (only switch survives)") + func lockingOffLockRuleInert() { + let (engine, provider, monitor, _) = makeEngine(current: us, frontmost: "com.foo.App") + engine.apply(LockConfiguration( + isEnabled: true, lockingEnabled: false, + appRules: [AppRule(bundleID: "com.apple.Terminal", mode: .locked, lockedSourceID: abc)] + )) + monitor.activate("com.apple.Terminal") + #expect(provider.current == us) // lock rule does not pin + #expect(provider.selectCalls.isEmpty) + } + + @Test("turning the lock sub-toggle on engages the global default") + func togglingLockingOnEngages() { + let (engine, provider, _, _) = makeEngine(current: abc, frontmost: "com.foo.App") + let base = LockConfiguration(isEnabled: true, lockingEnabled: false, defaultSourceID: us) + engine.apply(base) + #expect(provider.current == abc) // off → not pinned + + var on = base; on.lockingEnabled = true + engine.apply(on, reason: .lockEngaged) + #expect(provider.current == us) // now pinned + #expect(provider.selectCalls == [us]) + } + + @Test("master off gates everything regardless of the lock sub-toggle") + func masterOffGatesEverything() { + let (engine, provider, monitor, _) = makeEngine(current: us, frontmost: "com.foo.App") + // Master off but lock sub-toggle nominally on: still fully idle. + engine.apply(LockConfiguration( + isEnabled: false, lockingEnabled: true, defaultSourceID: abc, + appRules: [AppRule(bundleID: "com.apple.Terminal", mode: .switched, lockedSourceID: abc)] + )) + #expect(provider.current == us) + monitor.activate("com.apple.Terminal") + #expect(provider.selectCalls.isEmpty) // neither locks nor switches + #expect(provider.current == us) + } + @Test("toggling the lock OFF then ON re-fires the one-shot; two ON applies do not") func offOnReFiresButRepeatOnDoesNot() { let (engine, provider, _, _) = makeEngine(current: us, frontmost: "com.apple.Terminal") diff --git a/Tests/LockIMEKitTests/URLCommandParserTests.swift b/Tests/LockIMEKitTests/URLCommandParserTests.swift index 56add4e..af575c6 100644 --- a/Tests/LockIMEKitTests/URLCommandParserTests.swift +++ b/Tests/LockIMEKitTests/URLCommandParserTests.swift @@ -37,6 +37,18 @@ struct URLCommandParserTests { #expect(command("lockime://toggle") == .toggleLock) } + @Test("set-locking parses the tri-state flag (with the locking alias)") + func setLockingFlag() { + #expect(command("lockime://set-locking?enabled=true") == .setLocking(.on)) + #expect(command("lockime://set-locking?enabled=on") == .setLocking(.on)) + #expect(command("lockime://set-locking?enabled=false") == .setLocking(.off)) + #expect(command("lockime://set-locking?enabled=toggle") == .setLocking(.toggle)) + // `locking` is an accepted alias for the command token. + #expect(command("lockime://locking?enabled=off") == .setLocking(.off)) + #expect(failure("lockime://set-locking") == .missingParameter("enabled")) + #expect(failure("lockime://set-locking?enabled=maybe") == .invalidParameter(name: "enabled", value: "maybe")) + } + @Test("the command token is case-insensitive") func caseInsensitiveToken() { #expect(command("lockime://LOCK") == .lock) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 539220a..ada0d76 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -147,16 +147,18 @@ is checked) keeps the gutter reserved at a constant width, so the menu never grows or shrinks as the lock toggles — and NSMenu drops SwiftUI's `.opacity` on a Label's system-image icon, so the slot must swap the image itself, not hide a symbol. Clicking an unchecked source locks to it (`AppState.lockToSource` sets -the target *and* enables locking in one commit, re-resolving and flipping the -active source immediately); clicking the checked source disables locking -(`setMasterEnabled(false)`), leaving the target remembered. This is the same +the target *and* turns the master **and** the lock sub-toggle on in one commit, +re-resolving and flipping the active source immediately); clicking the checked +source clears just the global lock target (`setDefaultSource(nil)`) — the app and +any one-shot switch rules stay live. This is the same write path as the App Rules "Global default" picker. Source names are `Text(verbatim:)` system strings, not catalog keys. The engine keeps the list live via a second `InputSourceChangeObserver(.enabledSourcesChanged)`, so adding/removing an input source in System Settings updates both this menu and the Settings pickers. The -global toggle-lock shortcut (Settings ▸ Shortcuts) is unchanged — it flips -locking on/off against the remembered target, independent of this list. +global toggle-lock shortcut (Settings ▸ Shortcuts) flips the master +(Enable LockIME) on/off; `lockingEnabled` persists across toggles, so a +pure-switch user stays in pure-switch mode. Collapse Settings / Check for Updates / About into one `Section` (fewer dividers). Keep `.keyboardShortcut` hints. Zero custom color — NSMenu supplies everything. (Active-scope `.badge` is a nice-to-have — verify it renders on the real macOS 26 @@ -173,9 +175,15 @@ user to **Permissions** for the single Accessibility grant); the root view's `onDisappear` (window close, not a tab switch) is the abandon signal that stops the grant watcher. -- **General:** master toggle (`withAnimation(DS.Motion.toggle)` + - `.contentTransition(.symbolEffect(.replace))` on the lock label), current source - + activation count via `LabeledContent`, launch-at-login, language. +- **General:** two stacked toggles — the master **Enable LockIME** + (`config.isEnabled`, gates everything) and the subordinate **Enable locking** + (`config.lockingEnabled`, the continuous lock), the latter `.disabled` while the + master is off (HIG dependency: indent + dim). Locking off is the "act like Input + Source Pro" mode — switch rules still fire, nothing is pinned. Both animate with + `withAnimation(DS.Motion.toggle)`. Then current source + activation count via + `LabeledContent`, launch-at-login, language. The padlock surfaces (tray/About + icon, menu glyph/header, checkmark) track `isLocked = isEnabled && + lockingEnabled`, so they look unchanged for anyone who leaves locking on. - **App / URL Rules:** rows via shared `AppRowLabel(bundleID:)` (icon 22 + name `.body` + bundle ID `.caption2 .secondary`). Empty state = `ContentUnavailableView` with an action. Rows `.transition(.move(edge:.top) diff --git a/docs/README/README.de.md b/docs/README/README.de.md index e7f37eb..bc46fee 100644 --- a/docs/README/README.de.md +++ b/docs/README/README.de.md @@ -51,9 +51,10 @@ Oder lade die zu deinem Mac passende `.dmg`-Datei (`-arm64` für Apple silicon, - **Sofortiges Wieder-Sperren** — schaltet die aktive Eingabequelle in dem Moment zurück, in dem du (oder eine andere App) sie wechselst, global oder pro App. - **Sperren oder wechseln** — Regeln pro App und pro URL können eine Eingabequelle *sperren* (bei jeder Abweichung erneut angewendet) oder einmalig dorthin *wechseln*, sobald du die App oder Seite aktivierst, und dich danach frei wählen lassen. +- **Global sperren oder einfach wechseln** — ein einziger Schalter **LockIME aktivieren** treibt alles an; ein untergeordneter Schalter **Sperre aktivieren** steuert ausschließlich die kontinuierliche Sperre. Schalte die Sperre aus, um LockIME als reinen Umschalter pro App/pro Seite zu nutzen — es schaltet dich hinein, lässt dich danach frei und fixiert nichts. - **Flexibler URL-Abgleich** — Regeln pro URL (erweiterter Modus) passen über eine Domain und ihre Subdomains, eine exakte Domain, ein Domain-Schlüsselwort oder einen regulären Ausdruck über die vollständige URL und greifen in einer Prioritätsreihenfolge, die du per Ziehen anordnest — der erste Treffer gewinnt. - **Steuerung über die Menüleiste** — aktivieren/deaktivieren, die gesperrte Eingabequelle wechseln, die aktuelle Eingabequelle einsehen und die Auslösungen direkt in der Menüleiste verfolgen. -- **Tastatur-Kurzbefehle** — konfigurierbare globale Kurzbefehle zum Ein- und Ausschalten der Sperre und zum Durchschalten der gesperrten Eingabequelle sowie App-spezifische Kurzbefehle, um die Regel der vordersten App durchzuschalten oder zu entfernen. +- **Tastatur-Kurzbefehle** — konfigurierbare globale Kurzbefehle zum Ein- und Ausschalten von LockIME und zum Durchschalten der gesperrten Eingabequelle sowie App-spezifische Kurzbefehle, um die Regel der vordersten App durchzuschalten oder zu entfernen. - **Start bei Anmeldung** — startet automatisch beim Anmelden (standardmäßig aus). - **Heller & dunkler Modus** — eine einheitliche, systemnative Designsprache, die sich an helles und dunkles Erscheinungsbild anpasst, plus ein maßgeschneidertes App-Symbol. Siehe [docs/DESIGN.md](../DESIGN.md). - **Sprachwechsel zur Laufzeit** — wechsle sofort zwischen 9 Sprachen, ohne Neustart: English, 简体中文, 繁體中文, 日本語, Français, Deutsch, Español, Português, Русский. diff --git a/docs/README/README.es.md b/docs/README/README.es.md index 0a961e3..d6712d6 100644 --- a/docs/README/README.es.md +++ b/docs/README/README.es.md @@ -55,9 +55,10 @@ En cualquier caso, la aplicación se mantiene actualizada mediante Sparkle. - **Rebloqueo instantáneo** — devuelve la fuente de entrada activa a la bloqueada en el momento en que tú (u otra aplicación) la cambias, globalmente o por aplicación. - **Bloquear o cambiar** — las reglas por aplicación y por URL pueden *bloquear* una fuente de entrada (se vuelve a aplicar cada vez que se desvía) o solo *cambiar* a ella una vez cuando activas la aplicación o la página, y luego dejarte cambiarla libremente. +- **Bloquear de forma global, o solo cambiar** — un único interruptor **Activar LockIME** lo controla todo; un conmutador subordinado **Activar el bloqueo** gobierna únicamente el bloqueo continuo. Desactiva el bloqueo para usar LockIME como un cambiador puro por aplicación/por sitio — te cambia al entrar y luego te deja libre, sin fijar nada. - **Coincidencia de URL flexible** — las reglas por URL (modo mejorado) coinciden por un dominio y sus subdominios, por un dominio exacto, por una palabra clave de dominio o por una expresión regular sobre la URL completa, y se aplican en el orden de prioridad que tú arrastras para organizar — la primera coincidencia gana. - **Control desde la barra de menús** — activa/desactiva, cambia la fuente de entrada bloqueada, consulta la fuente actual y sigue el contador de activaciones desde la barra de menús. -- **Atajos de teclado** — atajos globales configurables para activar/desactivar el bloqueo y recorrer la fuente de entrada bloqueada, además de atajos por aplicación para recorrer o eliminar la regla de la aplicación en primer plano. +- **Atajos de teclado** — atajos globales configurables para activar/desactivar LockIME y recorrer la fuente de entrada bloqueada, además de atajos por aplicación para recorrer o eliminar la regla de la aplicación en primer plano. - **Arranque al iniciar sesión** — se inicia automáticamente al iniciar sesión (desactivado por defecto). - **Modo claro y oscuro** — un lenguaje de diseño unificado y nativo del sistema que se adapta a la apariencia clara y oscura, además de un icono de aplicación a medida. Ver [docs/DESIGN.md](../DESIGN.md). - **Cambio de idioma en vivo** — cambia al instante entre 9 idiomas, sin reiniciar: English, 简体中文, 繁體中文, 日本語, Français, Deutsch, Español, Português, Русский. diff --git a/docs/README/README.fr.md b/docs/README/README.fr.md index fcb35ce..a07cb6d 100644 --- a/docs/README/README.fr.md +++ b/docs/README/README.fr.md @@ -51,9 +51,10 @@ Ou téléchargez le `.dmg` correspondant à votre Mac (`-arm64` pour Apple silic - **Reverrouillage instantané** — rebascule la source de saisie active dès que vous (ou une autre application) la changez, globalement ou par application. - **Verrouiller ou basculer** — les règles par application et par URL peuvent *verrouiller* une source de saisie (réappliquée dès qu'elle dévie) ou simplement y *basculer* une fois lorsque vous activez l'application ou la page, puis vous laisser libre de la changer. +- **Verrouiller globalement, ou simplement basculer** — un unique interrupteur **Activer LockIME** alimente tout ; une bascule subordonnée **Activer le verrouillage** ne contrôle que le verrouillage continu. Désactivez le verrouillage pour utiliser LockIME comme un simple commutateur par application/par site — il vous bascule en entrant, puis vous laisse libre, sans rien épingler. - **Correspondance d'URL flexible** — les règles par URL (mode renforcé) correspondent par un domaine et ses sous-domaines, un domaine exact, un mot-clé de domaine, ou une expression régulière sur l'URL entière, et s'appliquent dans un ordre de priorité que vous organisez par glisser-déposer — la première correspondance l'emporte. - **Contrôle depuis la barre de menus** — activer/désactiver, changer la source de saisie verrouillée, voir la source actuelle et suivre le nombre d'activations depuis la barre de menus. -- **Raccourcis clavier** — des raccourcis globaux configurables pour activer/désactiver le verrouillage et faire défiler la source de saisie verrouillée, ainsi que des raccourcis par application pour faire défiler ou supprimer la règle de l’application au premier plan. +- **Raccourcis clavier** — des raccourcis globaux configurables pour activer/désactiver LockIME et faire défiler la source de saisie verrouillée, ainsi que des raccourcis par application pour faire défiler ou supprimer la règle de l’application au premier plan. - **Lancement à la connexion** — démarre automatiquement à l'ouverture de session (désactivé par défaut). - **Mode clair et sombre** — un langage de design unifié et natif du système, qui s'adapte aux apparences claire et sombre, avec une icône d'application sur mesure. Voir [docs/DESIGN.md](../DESIGN.md). - **Changement de langue à chaud** — basculez instantanément entre 9 langues, sans redémarrage : English, 简体中文, 繁體中文, 日本語, Français, Deutsch, Español, Português, Русский. diff --git a/docs/README/README.ja.md b/docs/README/README.ja.md index 1ad623a..b6ff8b2 100644 --- a/docs/README/README.ja.md +++ b/docs/README/README.ja.md @@ -51,9 +51,10 @@ brew install --cask oomol-lab/tap/lockime - **即時再ロック**——あなた(または他のアプリ)が入力ソースを切り替えた瞬間に、ロック中のものへ切り戻します。グローバルにも、アプリごとにも。 - **ロックまたは切り替え**——アプリごと・URL ごとのルールは、入力ソースを*ロック*(ずれるたびに切り戻す)することも、アプリやページをアクティブにしたときに一度だけ*切り替え*て、その後は自由に変更できるようにすることもできます。 +- **グローバルにロック、または切り替えだけ**——**LockIME を有効にする**スイッチ一つですべてが動作し、その従属トグルである**ロックを有効にする**は継続ロックだけを制御します。ロックをオフにすると、LockIME を純粋なアプリごと / サイトごとの切り替え役として使えます——切り替えてから自由にさせ、何も固定しません。 - **柔軟な URL マッチング**——URL ごとのルール(拡張モード)は、ドメインとそのサブドメイン、完全一致のドメイン、ドメインのキーワード、または URL 全体に対する正規表現でマッチし、ドラッグして並べ替える優先順位順に適用されます——最初にマッチしたものが優先されます。 - **メニューバーからの操作**——メニューバーから有効化/無効化、ロック中の入力ソースの切り替え、現在の入力ソースの確認、作動回数の追跡。 -- **キーボードショートカット**——設定可能なグローバルショートカットでロックのオン/オフやロック中の入力ソースの切り替え(前 / 次)ができ、さらに最前面のアプリのルールを切り替えたり解除したりするアプリごとのショートカットも利用できます。 +- **キーボードショートカット**——設定可能なグローバルショートカットで LockIME のオン/オフやロック中の入力ソースの切り替え(前 / 次)ができ、さらに最前面のアプリのルールを切り替えたり解除したりするアプリごとのショートカットも利用できます。 - **ログイン時に起動**——ログイン時に自動的に起動(デフォルトはオフ)。 - **ライト & ダークモード**——ライト/ダーク外観に適応する、統一されたシステムネイティブなデザイン言語と、専用のアプリアイコン。[docs/DESIGN.md](../DESIGN.md) を参照。 - **ライブ言語切り替え**——9 言語を再起動なしで即座に切り替え:English、简体中文、繁體中文、日本語、Français、Deutsch、Español、Português、Русский。 diff --git a/docs/README/README.pt.md b/docs/README/README.pt.md index 4f124db..ac1d815 100644 --- a/docs/README/README.pt.md +++ b/docs/README/README.pt.md @@ -55,9 +55,10 @@ De qualquer forma, o app se mantém atualizado sozinho via Sparkle. - **Rebloqueio instantâneo** — devolve a fonte de entrada ativa para a bloqueada no momento em que você (ou outro app) a troca, globalmente ou por app. - **Bloquear ou alternar** — as regras por app e por URL podem *bloquear* uma fonte de entrada (reaplicada sempre que ela desvia) ou apenas *alternar* para ela uma vez quando você foca o app ou a página, deixando você livre para mudá-la depois. +- **Bloquear globalmente, ou apenas alternar** — um único interruptor **Ativar o LockIME** comanda tudo; um botão subordinado **Ativar o bloqueio** controla apenas o bloqueio contínuo. Desative o bloqueio para usar o LockIME como um alternador puro por app/por site — ele alterna você para a fonte, depois deixa você livre, sem fixar nada. - **Correspondência flexível de URL** — as regras por URL (modo aprimorado) correspondem por um domínio e seus subdomínios, por um domínio exato, por uma palavra-chave de domínio, ou por uma expressão regular sobre a URL inteira, e se aplicam em uma ordem de prioridade que você arrasta para organizar — a primeira correspondência vence. - **Controle pela barra de menus** — ative/desative, troque a fonte de entrada bloqueada, veja a fonte atual e acompanhe o contador de ativações pela barra de menus. -- **Atalhos de teclado** — atalhos globais configuráveis para ativar/desativar o bloqueio e percorrer a fonte de entrada bloqueada, além de atalhos por app para percorrer ou remover a regra do app em primeiro plano. +- **Atalhos de teclado** — atalhos globais configuráveis para ativar/desativar o LockIME e percorrer a fonte de entrada bloqueada, além de atalhos por app para percorrer ou remover a regra do app em primeiro plano. - **Iniciar no login** — inicia automaticamente quando você faz login (desativado por padrão). - **Modo claro e escuro** — uma linguagem de design unificada e nativa do sistema, que se adapta às aparências clara e escura, além de um ícone de app sob medida. Veja [docs/DESIGN.md](../DESIGN.md). - **Troca de idioma ao vivo** — alterne instantaneamente entre 9 idiomas, sem reiniciar: English, 简体中文, 繁體中文, 日本語, Français, Deutsch, Español, Português, Русский. diff --git a/docs/README/README.ru.md b/docs/README/README.ru.md index b866e9a..8a70d62 100644 --- a/docs/README/README.ru.md +++ b/docs/README/README.ru.md @@ -51,9 +51,10 @@ brew install --cask oomol-lab/tap/lockime - **Мгновенная повторная блокировка** — возвращает активный источник ввода к заблокированному в тот же момент, когда вы (или другое приложение) его меняете, — глобально или для каждого приложения. - **Блокировать или переключать** — правила для приложений и для URL могут *блокировать* источник ввода (повторно применяя его при любом отклонении) или один раз *переключать* на него при открытии приложения или страницы, а затем не мешать вам его менять. +- **Блокировать глобально или просто переключать** — один переключатель **Включить LockIME** управляет всем; подчинённый ему переключатель **Включить блокировку** управляет только непрерывной блокировкой. Выключите блокировку, чтобы использовать LockIME как чистый переключатель для отдельных приложений и сайтов — он переключает вас при входе, а затем оставляет вас свободными, ничего не закрепляя. - **Гибкое сопоставление URL** — правила для URL (расширенный режим) сопоставляются по домену и его поддоменам, по точному домену, по ключевому слову домена или по регулярному выражению над всем URL, и применяются в порядке приоритета, который вы задаёте перетаскиванием, — побеждает первое совпадение. - **Управление из строки меню** — включение/выключение, смена заблокированного источника ввода, просмотр текущего источника и счётчик срабатываний прямо в строке меню. -- **Сочетания клавиш** — настраиваемые глобальные сочетания для включения/выключения блокировки и перебора заблокированного источника ввода, а также сочетания для отдельных приложений, позволяющие переключать или удалять правило активного приложения. +- **Сочетания клавиш** — настраиваемые глобальные сочетания для включения/выключения LockIME и перебора заблокированного источника ввода, а также сочетания для отдельных приложений, позволяющие переключать или удалять правило активного приложения. - **Запуск при входе в систему** — стартует автоматически при входе (по умолчанию выключено). - **Светлая и тёмная темы** — единый, нативный для системы язык дизайна, адаптирующийся к светлому и тёмному оформлению, плюс фирменная иконка приложения. См. [docs/DESIGN.md](../DESIGN.md). - **Смена языка на лету** — мгновенно переключайтесь между 9 языками без перезапуска: English, 简体中文, 繁體中文, 日本語, Français, Deutsch, Español, Português, Русский. diff --git a/docs/README/README.zh-CN.md b/docs/README/README.zh-CN.md index 7702844..85d4bcd 100644 --- a/docs/README/README.zh-CN.md +++ b/docs/README/README.zh-CN.md @@ -54,9 +54,10 @@ Mac 匹配的 `.dmg`(Apple silicon 选 `-arm64`,Intel 选 `-x86_64`)。 - **即时重新锁定**——每当你(或其他应用)切换输入源时,立即切回被锁定的那个,可全局或按应用生效。 - **锁定或切换**——按应用和按 URL 的规则既可以*锁定*某个输入源(一旦偏离就重新切回),也可以在你切到该应用或页面时只*切换*一次,之后任你自由更改。 +- **全局锁定,或仅切换**——一个 **启用 LockIME** 开关掌管全部;从属的 **启用锁定** 开关只控制持续锁定。把锁定关掉,就能将 LockIME 当作纯粹的按应用 / 按站点切换器来用——它会在你进入时为你切换,随后便放手让你自由,不固定任何东西。 - **灵活的 URL 匹配**——按 URL 规则(增强模式)可以按某个域名及其子域名、某个精确域名、某个域名关键词,或针对完整 URL 的正则表达式来匹配,并按你拖动排列出的优先级顺序生效——第一个命中者胜出。 - **菜单栏控制**——在菜单栏激活/停用、切换被锁定的输入源、查看当前输入源、追踪激活次数。 -- **键盘快捷键**——可配置的全局快捷键用于开关锁定、切换被锁定的输入源(上一个 / 下一个),以及针对当前最前台应用的快捷键,用于切换或移除该应用的规则。 +- **键盘快捷键**——可配置的全局快捷键用于开关 LockIME、切换被锁定的输入源(上一个 / 下一个),以及针对当前最前台应用的快捷键,用于切换或移除该应用的规则。 - **登录时启动**——登录后自动启动(默认关闭)。 - **浅色 / 深色模式**——统一的、系统原生的设计语言,自动适配浅色与深色外观,并配有定制应用图标。参见 [docs/DESIGN.md](../DESIGN.md)。 - **实时语言切换**——在 9 种语言间即时切换,无需重启:English、简体中文、繁體中文、日本語、Français、Deutsch、Español、Português、Русский。 diff --git a/docs/README/README.zh-TW.md b/docs/README/README.zh-TW.md index 28a978f..2508c76 100644 --- a/docs/README/README.zh-TW.md +++ b/docs/README/README.zh-TW.md @@ -51,9 +51,10 @@ brew install --cask oomol-lab/tap/lockime - **即時重新鎖定**——每當你(或其他應用程式)切換輸入法時,立即切回被鎖定的那個,可全域或依應用程式生效。 - **鎖定或切換**——各應用程式與各 URL 的規則既可*鎖定*某個輸入法(一旦偏離就重新切回),也可以在你切到該應用程式或頁面時只*切換*一次,之後任你自由變更。 +- **全域鎖定,或僅切換**——只要一個 **啟用 LockIME** 開關就能驅動一切;其下從屬的 **啟用鎖定** 切換則只控制持續鎖定。把鎖定關掉,就能把 LockIME 當作純粹的依應用程式/依站台切換器使用——它會在你切入時幫你切好,之後便放手讓你自由,什麼也不固定。 - **彈性的 URL 比對**——依 URL 規則(增強模式)可依一個網域及其子網域、一個確切網域、一個網域關鍵字,或一個涵蓋整個 URL 的正規表示式來比對,並依你拖曳排列的優先序套用——第一個比對到的勝出。 - **選單列控制**——在選單列啟用/停用、切換被鎖定的輸入法、檢視目前輸入法、追蹤觸發次數。 -- **鍵盤快速鍵**——可自訂的全域快速鍵用於開關鎖定、切換被鎖定的輸入法(上一個 / 下一個),以及針對目前最前台應用程式的快速鍵,用於切換或移除該應用程式的規則。 +- **鍵盤快速鍵**——可自訂的全域快速鍵用於開關 LockIME、切換被鎖定的輸入法(上一個 / 下一個),以及針對目前最前台應用程式的快速鍵,用於切換或移除該應用程式的規則。 - **登入時啟動**——登入後自動啟動(預設關閉)。 - **淺色 / 深色模式**——統一的、系統原生的設計語言,自動適應淺色與深色外觀,並配有專屬應用程式圖示。參見 [docs/DESIGN.md](../DESIGN.md)。 - **即時語言切換**——在 9 種語言間即時切換,無需重新啟動:English、简体中文、繁體中文、日本語、Français、Deutsch、Español、Português、Русский。 diff --git a/docs/URL-Scheme-API/README.de.md b/docs/URL-Scheme-API/README.de.md index d1f95a1..f97523e 100644 --- a/docs/URL-Scheme-API/README.de.md +++ b/docs/URL-Scheme-API/README.de.md @@ -84,13 +84,21 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +Der Hauptschalter (`lock` / `unlock` / `toggle-lock`) schaltet **LockIME** ein oder +aus — er steuert alles (sowohl das Sperren als auch das Wechseln). Der +`set-locking`-Unterschalter, der dem Hauptschalter untergeordnet ist, steuert allein +die **kontinuierliche Sperre**: Schalte sie aus, um keine Quelle mehr zu fixieren, +während einmalige Wechselregeln weiterhin auslösen — der Modus „wie ein reiner +Umschalter handeln". | Command | Parameters | Effect | |---|---|---| -| `lock` | — | Die Hauptsperre **einschalten**. | -| `unlock` | — | Die Hauptsperre **ausschalten**. | -| `toggle-lock` *(alias `toggle`)* | — | Die Hauptsperre umschalten. | +| `lock` | — | **LockIME** (der Hauptschalter) **einschalten** — wendet deine Regeln an. | +| `unlock` | — | **LockIME** (der Hauptschalter) **ausschalten** — vollständig untätig. | +| `toggle-lock` *(alias `toggle`)* | — | Den Hauptschalter ein/aus umschalten. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Die **kontinuierliche Sperre** ein-/ausschalten (oder umschalten). Aus ⇒ nichts wird fixiert, aber Wechselregeln lösen weiterhin aus. Wirkungslos, solange der Hauptschalter aus ist. | ### Global input source @@ -189,6 +197,8 @@ Abfragebefehle geben eine JSON-Nutzlast über den `x-success`-Rückruf zurück ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -203,6 +213,9 @@ Abfragebefehle geben eine JSON-Nutzlast über den `x-success`-Rückruf zurück } ``` +`enabled` ist der Hauptschalter („LockIME aktivieren"); `lockingEnabled` ist der +Unterschalter für die kontinuierliche Sperre; `locked` ist nur dann `true`, wenn +beide eingeschaltet sind (tatsächlich eine Sperre in Kraft ist). `currentSource`, `defaultSource` und `frontmostApp` sind nur vorhanden, wenn sie bekannt sind. diff --git a/docs/URL-Scheme-API/README.es.md b/docs/URL-Scheme-API/README.es.md index 9f289f8..b429e49 100644 --- a/docs/URL-Scheme-API/README.es.md +++ b/docs/URL-Scheme-API/README.es.md @@ -78,13 +78,20 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +El maestro (`lock` / `unlock` / `toggle-lock`) activa o desactiva **LockIME** — controla +el acceso a todo (tanto el bloqueo como el cambio). El subconmutador `set-locking`, +subordinado al maestro, controla únicamente el **bloqueo continuo**: desactívalo para dejar +de fijar cualquier fuente mientras las reglas de cambio de una sola vez siguen disparándose — +el modo «actuar como un cambiador puro». | Command | Parameters | Effect | |---|---|---| -| `lock` | — | Activa el bloqueo maestro (**on**). | -| `unlock` | — | Desactiva el bloqueo maestro (**off**). | -| `toggle-lock` *(alias `toggle`)* | — | Invierte el bloqueo maestro. | +| `lock` | — | Activa **LockIME** (el maestro) — aplica tus reglas. | +| `unlock` | — | Desactiva **LockIME** (el maestro) — totalmente inactivo. | +| `toggle-lock` *(alias `toggle`)* | — | Invierte el maestro (on/off). | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Activa o desactiva el **bloqueo continuo** (o lo invierte). Off ⇒ no se fija nada, pero las reglas de cambio siguen disparándose. Sin efecto mientras el maestro esté desactivado. | ### Global input source @@ -180,6 +187,8 @@ Los comandos de consulta devuelven una carga útil JSON a través del callback ` ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -194,6 +203,8 @@ Los comandos de consulta devuelven una carga útil JSON a través del callback ` } ``` +`enabled` es el maestro («Activar LockIME»); `lockingEnabled` es el subconmutador del bloqueo +continuo; `locked` es `true` solo cuando ambos están activos (hay un bloqueo realmente en vigor). `currentSource`, `defaultSource` y `frontmostApp` están presentes solo cuando se conocen. --- diff --git a/docs/URL-Scheme-API/README.fr.md b/docs/URL-Scheme-API/README.fr.md index 1b42f5b..13fd09a 100644 --- a/docs/URL-Scheme-API/README.fr.md +++ b/docs/URL-Scheme-API/README.fr.md @@ -79,13 +79,16 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +Le verrou principal (`lock` / `unlock` / `toggle-lock`) active ou désactive **LockIME** — il conditionne tout (le verrouillage comme la bascule). Le sous-commutateur `set-locking`, subordonné au verrou principal, ne contrôle que le **verrou continu** : désactivez-le pour cesser d'épingler toute source tandis que les règles de bascule ponctuelle continuent de se déclencher — le mode « se comporter comme un simple commutateur ». | Command | Parameters | Effect | |---|---|---| -| `lock` | — | Activer le verrou principal (**on**). | -| `unlock` | — | Désactiver le verrou principal (**off**). | -| `toggle-lock` *(alias `toggle`)* | — | Inverser le verrou principal. | +| `lock` | — | Activer **LockIME** (le verrou principal) — applique vos règles. | +| `unlock` | — | Désactiver **LockIME** (le verrou principal) — totalement inactif. | +| `toggle-lock` *(alias `toggle`)* | — | Inverser l'état du verrou principal. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Activer/désactiver le **verrou continu** (ou l'inverser). Désactivé ⇒ rien n'est épinglé, mais les règles de bascule se déclenchent quand même. Sans effet tant que le verrou principal est désactivé. | ### Global input source @@ -181,6 +184,8 @@ Les commandes de requête retournent une charge utile JSON via le rappel `x-succ ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -195,6 +200,7 @@ Les commandes de requête retournent une charge utile JSON via le rappel `x-succ } ``` +`enabled` est le verrou principal (« Activer LockIME ») ; `lockingEnabled` est le sous-commutateur de verrou continu ; `locked` vaut `true` uniquement lorsque les deux sont activés (un verrou est réellement en vigueur). `currentSource`, `defaultSource` et `frontmostApp` ne sont présents que lorsqu'ils sont connus. --- diff --git a/docs/URL-Scheme-API/README.ja.md b/docs/URL-Scheme-API/README.ja.md index 0b53f3b..e3927d8 100644 --- a/docs/URL-Scheme-API/README.ja.md +++ b/docs/URL-Scheme-API/README.ja.md @@ -77,13 +77,20 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +マスター(`lock` / `unlock` / `toggle-lock`)は **LockIME** をオン/オフします——これが +すべて(ロックと切り替えの両方)を制御します。マスターに従属するサブトグル +`set-locking` は、**継続ロック**だけを制御します:オフにすると、ワンショットの切り替え +ルールが発火し続ける一方で、どのソースも固定しなくなります——「純粋な切り替え役のように +振る舞う」モードです。 | Command | Parameters | Effect | |---|---|---| -| `lock` | — | マスターロックを**オン**にします。 | -| `unlock` | — | マスターロックを**オフ**にします。 | -| `toggle-lock` *(alias `toggle`)* | — | マスターロックを反転します。 | +| `lock` | — | **LockIME**(マスター)を**オン**にします——ルールを適用します。 | +| `unlock` | — | **LockIME**(マスター)を**オフ**にします——完全にアイドル状態になります。 | +| `toggle-lock` *(alias `toggle`)* | — | マスターのオン/オフを反転します。 | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | **継続ロック**をオン/オフ(または反転)します。オフ ⇒ 何も固定されませんが、切り替えルールは引き続き発火します。マスターがオフの間は効果がありません。 | ### Global input source @@ -181,6 +188,8 @@ LockIME は設計上、**UI を開くコマンドを一切公開していませ ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -195,6 +204,8 @@ LockIME は設計上、**UI を開くコマンドを一切公開していませ } ``` +`enabled` はマスター(「LockIME を有効にする」)です。`lockingEnabled` は継続ロックのサブトグル +です。`locked` は両方がオンのとき(実際にロックが効いているとき)にのみ `true` になります。 `currentSource`、`defaultSource`、`frontmostApp` は、判明している場合にのみ含まれます。 --- diff --git a/docs/URL-Scheme-API/README.md b/docs/URL-Scheme-API/README.md index 2449b48..448b6da 100644 --- a/docs/URL-Scheme-API/README.md +++ b/docs/URL-Scheme-API/README.md @@ -78,13 +78,20 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +The master (`lock` / `unlock` / `toggle-lock`) turns **LockIME** on or off — it +gates everything (both locking and switching). The `set-locking` sub-toggle, +subordinate to the master, controls only the **continuous lock**: turn it off to +stop pinning any source while one-shot switch rules keep firing — the "act like a +pure switcher" mode. | Command | Parameters | Effect | |---|---|---| -| `lock` | — | Turn the master lock **on**. | -| `unlock` | — | Turn the master lock **off**. | -| `toggle-lock` *(alias `toggle`)* | — | Flip the master lock. | +| `lock` | — | Turn **LockIME** (the master) **on** — applies your rules. | +| `unlock` | — | Turn **LockIME** (the master) **off** — fully idle. | +| `toggle-lock` *(alias `toggle`)* | — | Flip the master on/off. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Turn the **continuous lock** on/off (or flip it). Off ⇒ nothing is pinned, but switch rules still fire. No effect while the master is off. | ### Global input source @@ -179,6 +186,8 @@ Query commands return a JSON payload through the `x-success` callback (see ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -193,6 +202,8 @@ Query commands return a JSON payload through the `x-success` callback (see } ``` +`enabled` is the master ("Enable LockIME"); `lockingEnabled` is the continuous-lock +sub-toggle; `locked` is `true` only when both are on (a lock is actually in force). `currentSource`, `defaultSource`, and `frontmostApp` are present only when known. --- diff --git a/docs/URL-Scheme-API/README.pt.md b/docs/URL-Scheme-API/README.pt.md index 82f135e..f3ee38b 100644 --- a/docs/URL-Scheme-API/README.pt.md +++ b/docs/URL-Scheme-API/README.pt.md @@ -82,13 +82,20 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +O mestre (`lock` / `unlock` / `toggle-lock`) liga ou desliga o **LockIME** — ele +comanda tudo (tanto o bloqueio quanto a alternância). O sub-toggle `set-locking`, +subordinado ao mestre, controla apenas o **bloqueio contínuo**: desligue-o para +parar de fixar qualquer fonte enquanto as regras de alternância única continuam +disparando — o modo "agir como um alternador puro". | Command | Parameters | Effect | |---|---|---| -| `lock` | — | Liga o bloqueio mestre (**on**). | -| `unlock` | — | Desliga o bloqueio mestre (**off**). | -| `toggle-lock` *(alias `toggle`)* | — | Inverte o bloqueio mestre. | +| `lock` | — | Liga o **LockIME** (o mestre) — aplica suas regras. | +| `unlock` | — | Desliga o **LockIME** (o mestre) — totalmente ocioso. | +| `toggle-lock` *(alias `toggle`)* | — | Inverte o mestre (liga/desliga). | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Liga/desliga o **bloqueio contínuo** (ou o inverte). Desligado ⇒ nada é fixado, mas as regras de alternância ainda disparam. Sem efeito enquanto o mestre estiver desligado. | ### Global input source @@ -187,6 +194,8 @@ Os comandos de consulta retornam um payload JSON através do callback ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -201,6 +210,9 @@ Os comandos de consulta retornam um payload JSON através do callback } ``` +`enabled` é o mestre ("Ativar o LockIME"); `lockingEnabled` é o sub-toggle do bloqueio +contínuo; `locked` é `true` apenas quando ambos estão ligados (um bloqueio está de +fato em vigor). `currentSource`, `defaultSource` e `frontmostApp` estão presentes apenas quando conhecidos. --- diff --git a/docs/URL-Scheme-API/README.ru.md b/docs/URL-Scheme-API/README.ru.md index 633e380..4a4a169 100644 --- a/docs/URL-Scheme-API/README.ru.md +++ b/docs/URL-Scheme-API/README.ru.md @@ -80,13 +80,16 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +Главный переключатель (`lock` / `unlock` / `toggle-lock`) включает и выключает **LockIME** — он управляет всем (и блокировкой, и переключением). Подчинённый ему вспомогательный переключатель `set-locking` управляет только **непрерывной блокировкой**: выключите его, чтобы перестать закреплять какой-либо источник, пока продолжают срабатывать одноразовые правила переключения, — режим «вести себя как чистый переключатель». | Command | Parameters | Effect | |---|---|---| -| `lock` | — | **Включить** главную блокировку. | -| `unlock` | — | **Выключить** главную блокировку. | -| `toggle-lock` *(alias `toggle`)* | — | Переключить главную блокировку. | +| `lock` | — | **Включить** **LockIME** (главный переключатель) — применяются ваши правила. | +| `unlock` | — | **Выключить** **LockIME** (главный переключатель) — полностью бездействует. | +| `toggle-lock` *(alias `toggle`)* | — | Переключить главный переключатель (вкл/выкл). | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Включить/выключить **непрерывную блокировку** (или переключить её). Выкл ⇒ ничего не закрепляется, но правила переключения по-прежнему срабатывают. Не действует, пока главный переключатель выключен. | ### Global input source @@ -183,6 +186,8 @@ LockIME намеренно не предоставляет **никаких ко ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -197,6 +202,7 @@ LockIME намеренно не предоставляет **никаких ко } ``` +`enabled` — это главный переключатель («Включить LockIME»); `lockingEnabled` — вспомогательный переключатель непрерывной блокировки; `locked` равно `true`, только когда включены оба (блокировка действительно действует). `currentSource`, `defaultSource` и `frontmostApp` присутствуют только когда известны. --- diff --git a/docs/URL-Scheme-API/README.zh-CN.md b/docs/URL-Scheme-API/README.zh-CN.md index 543a113..d18ec93 100644 --- a/docs/URL-Scheme-API/README.zh-CN.md +++ b/docs/URL-Scheme-API/README.zh-CN.md @@ -74,13 +74,16 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +主开关(`lock` / `unlock` / `toggle-lock`)用于开启或关闭 **LockIME**——它掌控着一切(锁定与切换都包括在内)。从属于主开关的 `set-locking` 子开关只控制**持续锁定**:把它关掉,就会停止固定任何输入源,而一次性的切换规则照常触发——也就是“表现得像一个纯切换器”的模式。 | Command | Parameters | Effect | |---|---|---| -| `lock` | — | **开启**主锁定。 | -| `unlock` | — | **关闭**主锁定。 | -| `toggle-lock` *(alias `toggle`)* | — | 翻转主锁定。 | +| `lock` | — | 开启 **LockIME**(主开关)——应用你的规则。 | +| `unlock` | — | 关闭 **LockIME**(主开关)——完全闲置。 | +| `toggle-lock` *(alias `toggle`)* | — | 翻转主开关的开/关。 | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | 开启/关闭**持续锁定**(或翻转它)。关闭后 ⇒ 不固定任何东西,但切换规则仍会触发。当主开关关闭时无效。 | ### Global input source @@ -174,6 +177,8 @@ LockIME 刻意**不提供任何打开其 UI 的命令**(设置、关于、更 ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -188,6 +193,7 @@ LockIME 刻意**不提供任何打开其 UI 的命令**(设置、关于、更 } ``` +`enabled` 即主开关(“启用 LockIME”);`lockingEnabled` 是持续锁定子开关;只有当两者都开启时(确实有锁定在生效)`locked` 才为 `true`。 `currentSource`、`defaultSource` 和 `frontmostApp` 仅在已知时才会出现。 --- diff --git a/docs/URL-Scheme-API/README.zh-TW.md b/docs/URL-Scheme-API/README.zh-TW.md index 389ca2c..a4f43a7 100644 --- a/docs/URL-Scheme-API/README.zh-TW.md +++ b/docs/URL-Scheme-API/README.zh-TW.md @@ -75,13 +75,16 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D ## Command reference -### Master lock +### Enable & locking + +主控(`lock` / `unlock` / `toggle-lock`)負責開關 **LockIME**——它把關一切(鎖定與切換皆然)。從屬於主控的 `set-locking` 子切換則只控制**持續鎖定**:把它關掉,就能停止固定任何輸入法,同時一次性的切換規則仍會持續觸發——也就是「像純粹切換器那樣運作」的模式。 | Command | Parameters | Effect | |---|---|---| -| `lock` | — | 把主鎖切換為**開**。 | -| `unlock` | — | 把主鎖切換為**關**。 | -| `toggle-lock` *(alias `toggle`)* | — | 翻轉主鎖。 | +| `lock` | — | 開啟 **LockIME**(主控)——套用你的規則。 | +| `unlock` | — | 關閉 **LockIME**(主控)——完全閒置。 | +| `toggle-lock` *(alias `toggle`)* | — | 翻轉主控的開/關。 | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | 開啟/關閉**持續鎖定**(或翻轉它)。關閉 ⇒ 不固定任何輸入法,但切換規則仍會觸發。主控關閉時此指令無效。 | ### Global input source @@ -176,6 +179,8 @@ LockIME 刻意**不提供任何開啟其 UI 的指令**(Settings、About、更 ```json { + "enabled": true, + "lockingEnabled": true, "locked": true, "enhancedMode": false, "launchAtLogin": true, @@ -190,6 +195,7 @@ LockIME 刻意**不提供任何開啟其 UI 的指令**(Settings、About、更 } ``` +`enabled` 是主控(「啟用 LockIME」);`lockingEnabled` 是持續鎖定的子切換;`locked` 只有在兩者皆開啟(確實有鎖定正在生效)時才為 `true`。 `currentSource`、`defaultSource` 和 `frontmostApp` 只在已知時才會出現。 --- From 453c950cde974f17acab8d029bf7222fa6b688ce Mon Sep 17 00:00:00 2001 From: Kevin Cui Date: Sat, 27 Jun 2026 10:35:27 -0400 Subject: [PATCH 2/2] docs: clarify set-locking has no immediate runtime effect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set-locking persists lockingEnabled (and status reports it), so it still changes observable state while the master is off — it simply has no immediate runtime effect until the master is on. Sync the wording across all nine URL-Scheme-API docs. Signed-off-by: Kevin Cui --- docs/URL-Scheme-API/README.de.md | 2 +- docs/URL-Scheme-API/README.es.md | 2 +- docs/URL-Scheme-API/README.fr.md | 2 +- docs/URL-Scheme-API/README.ja.md | 2 +- docs/URL-Scheme-API/README.md | 2 +- docs/URL-Scheme-API/README.pt.md | 2 +- docs/URL-Scheme-API/README.ru.md | 2 +- docs/URL-Scheme-API/README.zh-CN.md | 2 +- docs/URL-Scheme-API/README.zh-TW.md | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/URL-Scheme-API/README.de.md b/docs/URL-Scheme-API/README.de.md index f97523e..02b6083 100644 --- a/docs/URL-Scheme-API/README.de.md +++ b/docs/URL-Scheme-API/README.de.md @@ -98,7 +98,7 @@ Umschalter handeln". | `lock` | — | **LockIME** (der Hauptschalter) **einschalten** — wendet deine Regeln an. | | `unlock` | — | **LockIME** (der Hauptschalter) **ausschalten** — vollständig untätig. | | `toggle-lock` *(alias `toggle`)* | — | Den Hauptschalter ein/aus umschalten. | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Die **kontinuierliche Sperre** ein-/ausschalten (oder umschalten). Aus ⇒ nichts wird fixiert, aber Wechselregeln lösen weiterhin aus. Wirkungslos, solange der Hauptschalter aus ist. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Die **kontinuierliche Sperre** ein-/ausschalten (oder umschalten). Aus ⇒ nichts wird fixiert, aber Wechselregeln lösen weiterhin aus. Ohne unmittelbare Laufzeitwirkung, solange der Hauptschalter aus ist. | ### Global input source diff --git a/docs/URL-Scheme-API/README.es.md b/docs/URL-Scheme-API/README.es.md index b429e49..ad9f4c0 100644 --- a/docs/URL-Scheme-API/README.es.md +++ b/docs/URL-Scheme-API/README.es.md @@ -91,7 +91,7 @@ el modo «actuar como un cambiador puro». | `lock` | — | Activa **LockIME** (el maestro) — aplica tus reglas. | | `unlock` | — | Desactiva **LockIME** (el maestro) — totalmente inactivo. | | `toggle-lock` *(alias `toggle`)* | — | Invierte el maestro (on/off). | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Activa o desactiva el **bloqueo continuo** (o lo invierte). Off ⇒ no se fija nada, pero las reglas de cambio siguen disparándose. Sin efecto mientras el maestro esté desactivado. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Activa o desactiva el **bloqueo continuo** (o lo invierte). Off ⇒ no se fija nada, pero las reglas de cambio siguen disparándose. Sin efecto inmediato en tiempo de ejecución mientras el maestro esté desactivado. | ### Global input source diff --git a/docs/URL-Scheme-API/README.fr.md b/docs/URL-Scheme-API/README.fr.md index 13fd09a..1b7c03a 100644 --- a/docs/URL-Scheme-API/README.fr.md +++ b/docs/URL-Scheme-API/README.fr.md @@ -88,7 +88,7 @@ Le verrou principal (`lock` / `unlock` / `toggle-lock`) active ou désactive **L | `lock` | — | Activer **LockIME** (le verrou principal) — applique vos règles. | | `unlock` | — | Désactiver **LockIME** (le verrou principal) — totalement inactif. | | `toggle-lock` *(alias `toggle`)* | — | Inverser l'état du verrou principal. | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Activer/désactiver le **verrou continu** (ou l'inverser). Désactivé ⇒ rien n'est épinglé, mais les règles de bascule se déclenchent quand même. Sans effet tant que le verrou principal est désactivé. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Activer/désactiver le **verrou continu** (ou l'inverser). Désactivé ⇒ rien n'est épinglé, mais les règles de bascule se déclenchent quand même. Sans effet immédiat à l'exécution tant que le verrou principal est désactivé. | ### Global input source diff --git a/docs/URL-Scheme-API/README.ja.md b/docs/URL-Scheme-API/README.ja.md index e3927d8..9420719 100644 --- a/docs/URL-Scheme-API/README.ja.md +++ b/docs/URL-Scheme-API/README.ja.md @@ -90,7 +90,7 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D | `lock` | — | **LockIME**(マスター)を**オン**にします——ルールを適用します。 | | `unlock` | — | **LockIME**(マスター)を**オフ**にします——完全にアイドル状態になります。 | | `toggle-lock` *(alias `toggle`)* | — | マスターのオン/オフを反転します。 | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | **継続ロック**をオン/オフ(または反転)します。オフ ⇒ 何も固定されませんが、切り替えルールは引き続き発火します。マスターがオフの間は効果がありません。 | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | **継続ロック**をオン/オフ(または反転)します。オフ ⇒ 何も固定されませんが、切り替えルールは引き続き発火します。マスターがオフの間は即時の実行時効果はありません。 | ### Global input source diff --git a/docs/URL-Scheme-API/README.md b/docs/URL-Scheme-API/README.md index 448b6da..513ac80 100644 --- a/docs/URL-Scheme-API/README.md +++ b/docs/URL-Scheme-API/README.md @@ -91,7 +91,7 @@ pure switcher" mode. | `lock` | — | Turn **LockIME** (the master) **on** — applies your rules. | | `unlock` | — | Turn **LockIME** (the master) **off** — fully idle. | | `toggle-lock` *(alias `toggle`)* | — | Flip the master on/off. | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Turn the **continuous lock** on/off (or flip it). Off ⇒ nothing is pinned, but switch rules still fire. No effect while the master is off. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Turn the **continuous lock** on/off (or flip it). Off ⇒ nothing is pinned, but switch rules still fire. No immediate runtime effect while the master is off. | ### Global input source diff --git a/docs/URL-Scheme-API/README.pt.md b/docs/URL-Scheme-API/README.pt.md index f3ee38b..6dead1c 100644 --- a/docs/URL-Scheme-API/README.pt.md +++ b/docs/URL-Scheme-API/README.pt.md @@ -95,7 +95,7 @@ disparando — o modo "agir como um alternador puro". | `lock` | — | Liga o **LockIME** (o mestre) — aplica suas regras. | | `unlock` | — | Desliga o **LockIME** (o mestre) — totalmente ocioso. | | `toggle-lock` *(alias `toggle`)* | — | Inverte o mestre (liga/desliga). | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Liga/desliga o **bloqueio contínuo** (ou o inverte). Desligado ⇒ nada é fixado, mas as regras de alternância ainda disparam. Sem efeito enquanto o mestre estiver desligado. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Liga/desliga o **bloqueio contínuo** (ou o inverte). Desligado ⇒ nada é fixado, mas as regras de alternância ainda disparam. Sem efeito imediato em tempo de execução enquanto o mestre estiver desligado. | ### Global input source diff --git a/docs/URL-Scheme-API/README.ru.md b/docs/URL-Scheme-API/README.ru.md index 4a4a169..43e43bb 100644 --- a/docs/URL-Scheme-API/README.ru.md +++ b/docs/URL-Scheme-API/README.ru.md @@ -89,7 +89,7 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D | `lock` | — | **Включить** **LockIME** (главный переключатель) — применяются ваши правила. | | `unlock` | — | **Выключить** **LockIME** (главный переключатель) — полностью бездействует. | | `toggle-lock` *(alias `toggle`)* | — | Переключить главный переключатель (вкл/выкл). | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Включить/выключить **непрерывную блокировку** (или переключить её). Выкл ⇒ ничего не закрепляется, но правила переключения по-прежнему срабатывают. Не действует, пока главный переключатель выключен. | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | Включить/выключить **непрерывную блокировку** (или переключить её). Выкл ⇒ ничего не закрепляется, но правила переключения по-прежнему срабатывают. Не действует немедленно во время выполнения, пока главный переключатель выключен. | ### Global input source diff --git a/docs/URL-Scheme-API/README.zh-CN.md b/docs/URL-Scheme-API/README.zh-CN.md index d18ec93..f82fa55 100644 --- a/docs/URL-Scheme-API/README.zh-CN.md +++ b/docs/URL-Scheme-API/README.zh-CN.md @@ -83,7 +83,7 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D | `lock` | — | 开启 **LockIME**(主开关)——应用你的规则。 | | `unlock` | — | 关闭 **LockIME**(主开关)——完全闲置。 | | `toggle-lock` *(alias `toggle`)* | — | 翻转主开关的开/关。 | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | 开启/关闭**持续锁定**(或翻转它)。关闭后 ⇒ 不固定任何东西,但切换规则仍会触发。当主开关关闭时无效。 | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | 开启/关闭**持续锁定**(或翻转它)。关闭后 ⇒ 不固定任何东西,但切换规则仍会触发。当主开关关闭时无即时运行效果。 | ### Global input source diff --git a/docs/URL-Scheme-API/README.zh-TW.md b/docs/URL-Scheme-API/README.zh-TW.md index a4f43a7..62a50ea 100644 --- a/docs/URL-Scheme-API/README.zh-TW.md +++ b/docs/URL-Scheme-API/README.zh-TW.md @@ -84,7 +84,7 @@ myapp://got-status?result=%7B%22locked%22%3Atrue%2C…%7D | `lock` | — | 開啟 **LockIME**(主控)——套用你的規則。 | | `unlock` | — | 關閉 **LockIME**(主控)——完全閒置。 | | `toggle-lock` *(alias `toggle`)* | — | 翻轉主控的開/關。 | -| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | 開啟/關閉**持續鎖定**(或翻轉它)。關閉 ⇒ 不固定任何輸入法,但切換規則仍會觸發。主控關閉時此指令無效。 | +| `set-locking` *(alias `locking`)* | `enabled` = `true` \| `false` \| `toggle` | 開啟/關閉**持續鎖定**(或翻轉它)。關閉 ⇒ 不固定任何輸入法,但切換規則仍會觸發。主控關閉時此指令無即時執行效果。 | ### Global input source