Skip to content

feat(api): add lockime:// URL scheme automation#25

Merged
BlackHole1 merged 2 commits into
mainfrom
add-api
Jun 16, 2026
Merged

feat(api): add lockime:// URL scheme automation#25
BlackHole1 merged 2 commits into
mainfrom
add-api

Conversation

@BlackHole1

Copy link
Copy Markdown
Member

Expose a lockime:// URL scheme so other apps, scripts, Shortcuts, and launchers can drive LockIME — toggle locking, retarget the input source, manage app/URL rules, and read state back. Query and action commands are parsed in the kit (URLCommandParser) and executed against the live AppState by a thin AppKit handler, with x-callback-url success and error callbacks.

The API is opt-in and off by default: the user must enable it under Settings ▸ General ▸ Automation, and the flag is stored in its own per-device UserDefaults key, deliberately kept out of LockConfiguration so it never travels through config export/import. Callback URLs are restricted to non-file, non-own schemes to avoid a confused-deputy.

On a URL-triggered cold launch, AppKit can deliver application(_:open:) before applicationDidFinishLaunching, so the handler calls the idempotent start() first to ensure the persisted config is loaded before any command can commit() over it.

Ship a full URL Scheme API reference in every SupportedLanguage and link it from the README.

Expose a lockime:// URL scheme so other apps, scripts, Shortcuts, and
launchers can drive LockIME — toggle locking, retarget the input
source, manage app/URL rules, and read state back. Query and action
commands are parsed in the kit (URLCommandParser) and executed against
the live AppState by a thin AppKit handler, with x-callback-url success
and error callbacks.

The API is opt-in and off by default: the user must enable it under
Settings ▸ General ▸ Automation, and the flag is stored in its own
per-device UserDefaults key, deliberately kept out of LockConfiguration
so it never travels through config export/import. Callback URLs are
restricted to non-file, non-own schemes to avoid a confused-deputy.

On a URL-triggered cold launch, AppKit can deliver application(_:open:)
before applicationDidFinishLaunching, so the handler calls the
idempotent start() first to ensure the persisted config is loaded
before any command can commit() over it.

Ship a full URL Scheme API reference in every SupportedLanguage and
link it from the README.

Signed-off-by: Kevin Cui <bh@bugs.cc>
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef00283d-f1bb-467a-ac9c-378e4699c0d5

📥 Commits

Reviewing files that changed from the base of the PR and between 95409c7 and fba6bc0.

📒 Files selected for processing (1)
  • Tests/LockIMEKitTests/LogStoreTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests/LockIMEKitTests/LogStoreTests.swift

Summary by CodeRabbit

  • New Features
    • Added lockime:// URL Scheme API for automation: lock/unlock, source switching, rule management, and state/query actions via x-callback-url.
    • Added an Automation section in Settings with an opt-in toggle (“URL Scheme API”).
  • Documentation
    • Published a complete URL Scheme API reference in multiple languages and linked it from the main product docs.
    • Updated translation guidance to keep README and lockime:// API reference conventions consistent.
  • Bug Fixes
    • Improved activation log attribution for URL-driven “API command” activations.

Walkthrough

This PR adds a lockime:// URL scheme API to LockIME. The scheme is registered in project.yml and Info.plist (with a lockime-dev:// variant for debug builds). A new URLCommand.swift in LockIMEKit defines the command model, parser, callback types, and safety policy. LockController gains a commandSwitch method for transient one-shot source switching without modifying the standing lock, surfaced as LockEngine.switchSourceOnce. AppState receives an apiEnabled flag (off by default), persistence helpers, and query/mutation APIs. URLCommandHandler wires all of this to AppKit, dispatching incoming URLs and delivering x-callback-url responses. AppDelegate implements application(_:open:) to route URLs to the handler. A Settings UI toggle and multilingual documentation (English plus 8 translations) are also included.

Sequence Diagram

sequenceDiagram
  participant ExternalCaller as External App / Script / Shortcuts
  participant macOS as macOS URL dispatch
  participant AppDelegate
  participant URLCommandHandler
  participant URLCommandParser
  participant AppState
  participant LockEngine
  participant NSWorkspace as NSWorkspace (callback)

  ExternalCaller->>macOS: open lockime://<command>?params
  macOS->>AppDelegate: application(_:open:urls)
  AppDelegate->>AppState: start() (ensure config loaded)
  AppDelegate->>URLCommandHandler: handle(url)
  URLCommandHandler->>AppState: check apiEnabled
  alt API disabled
    URLCommandHandler->>NSWorkspace: open x-error (api_disabled)
  else API enabled
    URLCommandHandler->>URLCommandParser: parse(url)
    URLCommandParser-->>URLCommandHandler: ParsedURLCommand
    URLCommandHandler->>AppState: execute command
    AppState->>LockEngine: switchSourceOnce / lock / rule update
    LockEngine-->>AppState: result
    AppState-->>URLCommandHandler: payload (nil or JSON dict)
    URLCommandHandler->>NSWorkspace: open x-success or x-error URL
    NSWorkspace->>ExternalCaller: callback URL opened
  end
Loading

Possibly Related PRs

  • oomol-lab/LockIME#7: Both PRs modify AppState to handle per-app source cycling and rule management — PR #7 adds keyboard-shortcut-driven cycling, which this PR refactors into the shared cycleAppSource(bundleID:direction:) helper also used by the URL API.
  • oomol-lab/LockIME#22: This PR's lockime:// URL-command handling for one-shot source switching builds on the one-shot "switch" rule/action behavior and transient switching APIs introduced in PR #22.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title follows the required format with type and scope, and is fully related to the main changeset which introduces a URL scheme API automation feature.
Description check ✅ Passed The description is directly related to the changeset and provides meaningful information about the URL scheme API implementation, opt-in behavior, security considerations, and documentation strategy.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch add-api

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Tests/LockIMEKitTests/LogStoreTests.swift`:
- Around line 65-67: The force-unwrapping of recent.first! and recent.last! on
the timestamp ordering expectation check can crash and hide test failures if the
earlier count expectation fails. Replace the force-unwraps with a `#require`
statement that verifies recent has at least one element before attempting to
access first and last properties in the timestamp ordering comparison. This
ensures graceful test failure with proper error reporting rather than a
force-unwrap crash.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a643d317-4f1d-4d97-8d23-7073353aa271

📥 Commits

Reviewing files that changed from the base of the PR and between f050428 and 95409c7.

📒 Files selected for processing (40)
  • AGENTS.md
  • README.md
  • Sources/LockIME/API/URLCommandHandler.swift
  • Sources/LockIME/AppDelegate.swift
  • Sources/LockIME/AppState.swift
  • Sources/LockIME/Info.plist
  • Sources/LockIME/Localizable.xcstrings
  • Sources/LockIME/UI/Settings/ActivationLogPane.swift
  • Sources/LockIME/UI/Settings/BackupSettingsPane.swift
  • Sources/LockIME/UI/Settings/GeneralSettingsPane.swift
  • Sources/LockIME/UI/SettingsRootView.swift
  • Sources/LockIME/Updates/LockIMEUserDriver.swift
  • Sources/LockIMEKit/API/URLCommand.swift
  • Sources/LockIMEKit/LockEngine/InputSource.swift
  • Sources/LockIMEKit/LockEngine/LockController.swift
  • Sources/LockIMEKit/LockEngine/LockEngine.swift
  • Sources/LockIMEKit/Logging/LogStore.swift
  • Sources/LockIMEKit/Logging/LogSubsystem.swift
  • Tests/LockIMEKitTests/LockControllerTests.swift
  • Tests/LockIMEKitTests/LogStoreTests.swift
  • Tests/LockIMEKitTests/URLCommandParserTests.swift
  • docs/DESIGN.md
  • docs/README/README.de.md
  • docs/README/README.es.md
  • docs/README/README.fr.md
  • docs/README/README.ja.md
  • docs/README/README.pt.md
  • docs/README/README.ru.md
  • docs/README/README.zh-CN.md
  • docs/README/README.zh-TW.md
  • docs/URL-Scheme-API/README.de.md
  • docs/URL-Scheme-API/README.es.md
  • docs/URL-Scheme-API/README.fr.md
  • docs/URL-Scheme-API/README.ja.md
  • docs/URL-Scheme-API/README.md
  • docs/URL-Scheme-API/README.pt.md
  • docs/URL-Scheme-API/README.ru.md
  • docs/URL-Scheme-API/README.zh-CN.md
  • docs/URL-Scheme-API/README.zh-TW.md
  • project.yml

Comment thread Tests/LockIMEKitTests/LogStoreTests.swift
A failing count assertion would otherwise crash on first!/last!, hiding
the real failure signal. Require the elements before the ordering check.
@BlackHole1 BlackHole1 merged commit 3f77e8f into main Jun 16, 2026
3 checks passed
@BlackHole1 BlackHole1 deleted the add-api branch June 16, 2026 05:00
BlackHole1 added a commit that referenced this pull request Jun 17, 2026
The committed screenshots predated the URL match-type badges and
drag-to-reorder priority (#26) and the General "Automation" section
(#25), so all three panes were out of date. Regenerate the full set
of 12 (General, App Rules, URL Rules x en/zh-CN x light/dark).

The URL Rules pane now shows four rules spanning every match type
(domain suffix, exact domain, domain keyword, URL regex) to surface
the new badges and the reorder caption. Images are now 2024x1600
because SettingsRootView gained a 600pt minimum content height.

Signed-off-by: Kevin Cui <bh@bugs.cc>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant