-
Notifications
You must be signed in to change notification settings - Fork 0
fix: auto-revert standard ABC switch back to Lexime #207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
54efda0
fix: guard against ESC causing input source switch to standard ABC
send 70e616a
fix: retry ESC→ABC revert check up to 5 times at 50ms intervals
send 4905ab0
feat: add InputSourceMonitor to detect unexpected ABC switch
send 21d8ecb
feat: auto-revert to Lexime after secure input release
send 4b13aaf
refactor: simplify InputSourceMonitor to auto-revert without macnotifier
send a4e2100
fix: remove unused leximeJapaneseID and update doc comment
send fc27f36
refactor: centralize input source IDs with runtime derivation
send 1bbb3db
docs: clarify IMKit mode ID vs TIS runtime ID distinction
send File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| import Carbon | ||
| import Foundation | ||
|
|
||
| /// Monitors input source changes and automatically reverts unexpected | ||
| /// switches to the standard ABC keyboard layout (which can happen due to | ||
| /// macOS IMKit race conditions) back to Lexime Roman, with secure input | ||
| /// awareness (polls for release before reverting). | ||
| final class InputSourceMonitor: NSObject { | ||
|
|
||
| private static let abcSourceID = "com.apple.keylayout.ABC" | ||
|
|
||
| /// Suppress notifications for this many seconds after init (avoid startup noise). | ||
| private static let startupQuietPeriod: TimeInterval = 5 | ||
| /// Delay before auto-reverting non-secure ABC switch. | ||
| private static let autoRevertDelay: TimeInterval = 0.3 | ||
| /// Polling interval for secure input release detection. | ||
| private static let secureInputPollInterval: TimeInterval = 0.5 | ||
| /// Maximum polling duration for secure input (give up after this). | ||
| private static let secureInputPollTimeout: TimeInterval = 60 | ||
|
|
||
| private let startTime = Date() | ||
| private var secureInputTimer: Timer? | ||
|
|
||
| func startMonitoring() { | ||
| DistributedNotificationCenter.default().addObserver( | ||
| self, | ||
| selector: #selector(inputSourceDidChange), | ||
| name: NSNotification.Name("com.apple.Carbon.TISNotifySelectedKeyboardInputSourceChanged"), | ||
| object: nil | ||
| ) | ||
| NSLog("Lexime: InputSourceMonitor started") | ||
| } | ||
|
|
||
| deinit { | ||
| secureInputTimer?.invalidate() | ||
| DistributedNotificationCenter.default().removeObserver(self) | ||
| } | ||
|
|
||
| // MARK: - Input Source Change Handling | ||
|
|
||
| @objc private func inputSourceDidChange() { | ||
| guard let source = TISCopyCurrentKeyboardInputSource()?.takeRetainedValue() else { return } | ||
| guard let idRef = TISGetInputSourceProperty(source, kTISPropertyInputSourceID) else { return } | ||
| let sourceID = Unmanaged<CFString>.fromOpaque(idRef).takeUnretainedValue() as String | ||
|
|
||
| guard sourceID == Self.abcSourceID else { return } | ||
|
|
||
| // Startup quiet period | ||
| guard Date().timeIntervalSince(startTime) >= Self.startupQuietPeriod else { | ||
| NSLog("Lexime: ABC detected but within startup quiet period, suppressing") | ||
| return | ||
| } | ||
|
|
||
| // If secure input is active (e.g. password field), poll for its | ||
| // release and auto-switch back to Lexime. | ||
| if IsSecureEventInputEnabled() { | ||
| NSLog("Lexime: ABC switch detected during secure input, polling for release") | ||
| startSecureInputPolling() | ||
| return | ||
| } | ||
|
|
||
| // Non-secure ABC switch (e.g. IMKit race on Eisu/ESC key). | ||
| // Auto-revert after a short delay. | ||
| NSLog("Lexime: unexpected ABC switch detected, auto-reverting in %.1fs", Self.autoRevertDelay) | ||
| DispatchQueue.main.asyncAfter(deadline: .now() + Self.autoRevertDelay) { [weak self] in | ||
| guard let self else { return } | ||
| self.selectLeximeRoman() | ||
| } | ||
| } | ||
|
|
||
| // MARK: - Secure Input Polling | ||
|
|
||
| private func startSecureInputPolling() { | ||
| secureInputTimer?.invalidate() | ||
| let deadline = Date().addingTimeInterval(Self.secureInputPollTimeout) | ||
| secureInputTimer = Timer.scheduledTimer( | ||
| withTimeInterval: Self.secureInputPollInterval, repeats: true | ||
| ) { [weak self] timer in | ||
| guard let self else { timer.invalidate(); return } | ||
| if !IsSecureEventInputEnabled() { | ||
| timer.invalidate() | ||
| self.secureInputTimer = nil | ||
| NSLog("Lexime: Secure input released, switching back to Lexime") | ||
| self.selectLeximeRoman() | ||
| } else if Date() >= deadline { | ||
| timer.invalidate() | ||
| self.secureInputTimer = nil | ||
| NSLog("Lexime: Secure input poll timed out") | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func selectLeximeRoman() { | ||
| let conditions = [ | ||
| kTISPropertyInputSourceID as String: LeximeInputSourceID.roman | ||
| ] as CFDictionary | ||
| guard let list = TISCreateInputSourceList(conditions, false)?.takeRetainedValue() | ||
| as? [TISInputSource], | ||
| let source = list.first else { return } | ||
| TISSelectInputSource(source) | ||
| } | ||
|
|
||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.