fix: avoid input source work in event tap

This commit is contained in:
root 2026-10-25 20:23:41 +03:00
parent 240bd4fcf6
commit f4606e44a4
3 changed files with 28 additions and 15 deletions

View file

@ -25,6 +25,8 @@ final class InputSourceManager {
let id: String let id: String
} }
private var orderedSources: [Source] = []
var currentDisplayName: String { var currentDisplayName: String {
guard let source = currentSource() else { guard let source = currentSource() else {
return "—" return "—"
@ -43,9 +45,13 @@ final class InputSourceManager {
return name return name
} }
func refresh() {
orderedSources = enabledKeyboardSourcesInUserOrder()
}
@discardableResult @discardableResult
func selectNextInputSource() -> Bool { func selectNextInputSource() -> Bool {
let sources = enabledKeyboardSourcesInUserOrder() let sources = orderedSources
guard sources.count > 1 else { guard sources.count > 1 else {
return false return false
} }
@ -163,6 +169,11 @@ final class MacOSInputController {
throw InputControllerError.accessibilityRequired throw InputControllerError.accessibilityRequired
} }
// Build the source order before the event tap starts. Enumerating TIS
// sources from inside a keyboard callback can block long enough for
// macOS to disable the tap while the user is typing quickly.
inputSources.refresh()
let eventMask = CGEventMask(1 << CGEventType.flagsChanged.rawValue) let eventMask = CGEventMask(1 << CGEventType.flagsChanged.rawValue)
let refcon = Unmanaged.passUnretained(self).toOpaque() let refcon = Unmanaged.passUnretained(self).toOpaque()
guard let tap = CGEvent.tapCreate( guard let tap = CGEvent.tapCreate(
@ -221,6 +232,10 @@ final class MacOSInputController {
onStateChanged?() onStateChanged?()
} }
func refreshInputSources() {
inputSources.refresh()
}
func openAccessibilitySettings() { func openAccessibilitySettings() {
guard let url = URL( guard let url = URL(
string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility" string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
@ -328,16 +343,11 @@ final class MacOSInputController {
case .none: case .none:
break break
case .switchToNextInputSource: case .switchToNextInputSource:
// TISSelectInputSource can synchronously trigger more keyboard // The source list is cached before the tap starts, so this path is
// notifications. Let the event tap return first so a rapid key + // deliberately synchronous: the next key must see the new source
// Caps sequence sees the new source before the next key is typed. // even when the user is typing continuously.
DispatchQueue.main.async { [weak self] in if inputSources.selectNextInputSource() {
guard let self, self.isRunning, !self.isPaused else { onStateChanged?()
return
}
if self.inputSources.selectNextInputSource() {
self.onStateChanged?()
}
} }
case .toggleCapsLock: case .toggleCapsLock:
postSyntheticCapsLock() postSyntheticCapsLock()

View file

@ -77,9 +77,11 @@ final class StatusBarController: NSObject, NSMenuDelegate {
} }
private func refreshController() { private func refreshController() {
if inputController.accessibilityTrusted, if inputController.accessibilityTrusted {
!inputController.isRunning { if !inputController.isRunning {
try? inputController.start() try? inputController.start()
}
inputController.refreshInputSources()
} }
update() update()
} }

View file

@ -56,7 +56,8 @@ tasks:
- grep -q "LSUIElement" Resources/Info.plist - grep -q "LSUIElement" Resources/Info.plist
- grep -q ".flagsChanged" Sources/CapswitchApp/MacOSInputController.swift - grep -q ".flagsChanged" Sources/CapswitchApp/MacOSInputController.swift
- grep -q "keyboardEventKeycode" Sources/CapswitchApp/MacOSInputController.swift - grep -q "keyboardEventKeycode" Sources/CapswitchApp/MacOSInputController.swift
- grep -q "DispatchQueue.main.async" Sources/CapswitchApp/MacOSInputController.swift - grep -q "orderedSources" Sources/CapswitchApp/MacOSInputController.swift
- grep -q "inputSources.refresh" Sources/CapswitchApp/MacOSInputController.swift
- grep -q "CGEvent.tapCreate" Sources/CapswitchApp/MacOSInputController.swift - grep -q "CGEvent.tapCreate" Sources/CapswitchApp/MacOSInputController.swift
- grep -q "CGEvent.tapEnable" Sources/CapswitchApp/MacOSInputController.swift - grep -q "CGEvent.tapEnable" Sources/CapswitchApp/MacOSInputController.swift
- test "$(grep -c "CGEventTapCreate" Sources/CapswitchApp/MacOSInputController.swift || true)" -eq 0 - test "$(grep -c "CGEventTapCreate" Sources/CapswitchApp/MacOSInputController.swift || true)" -eq 0