From af2525b33588ae832b9f84b1e14c701de65d42f7 Mon Sep 17 00:00:00 2001 From: Roman Varlakov Date: Wed, 29 Jul 2026 15:26:31 +0600 Subject: [PATCH] =?UTF-8?q?=D0=90=D0=BA=D1=82=D0=B8=D0=B2=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BD=D0=B0=D1=82=D0=B8=D0=B2?= =?UTF-8?q?=D0=BD=D1=8B=D0=B9=20=D1=87=D0=B5=D1=80=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BA=20=D0=B4=D0=BE=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WhisperKey/NativeCompositionBridge.swift | 32 ++++++++++++++++--- Sources/WhisperKeyInput/main.swift | 16 ++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/Sources/WhisperKey/NativeCompositionBridge.swift b/Sources/WhisperKey/NativeCompositionBridge.swift index 0406541..6793ae9 100644 --- a/Sources/WhisperKey/NativeCompositionBridge.swift +++ b/Sources/WhisperKey/NativeCompositionBridge.swift @@ -42,7 +42,9 @@ final class NativeCompositionBridge: NSObject { name: Message.ready, object: nil ) - registerInstalledInputMethod() + if inputSource(withID: Self.inputSourceID) == nil { + registerInstalledInputMethod() + } launchInputMethod() ping() } @@ -50,7 +52,6 @@ final class NativeCompositionBridge: NSObject { func activateForDictation() -> Bool { restoreWorkItem?.cancel() restoreWorkItem = nil - registerInstalledInputMethod() launchInputMethod() guard let source = inputSource(withID: Self.inputSourceID) else { return false } @@ -58,11 +59,11 @@ final class NativeCompositionBridge: NSObject { if currentID != Self.inputSourceID { previousInputSourceID = currentID } - guard TISEnableInputSource(source) == noErr, - TISSelectInputSource(source) == noErr else { + guard TISSelectInputSource(source) == noErr else { restoreInputSource() return false } + wakeInputMethod() ping() return true } @@ -146,6 +147,29 @@ final class NativeCompositionBridge: NSObject { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: work) } + private func wakeInputMethod() { + guard let source = CGEventSource(stateID: .hidSystemState) else { return } + let flags: CGEventFlags = [ + .maskCommand, + .maskAlternate, + .maskControl, + .maskShift, + ] + guard let keyDown = CGEvent( + keyboardEventSource: source, + virtualKey: CGKeyCode(kVK_F20), + keyDown: true + ), let keyUp = CGEvent( + keyboardEventSource: source, + virtualKey: CGKeyCode(kVK_F20), + keyDown: false + ) else { return } + keyDown.flags = flags + keyUp.flags = flags + keyDown.post(tap: .cghidEventTap) + keyUp.post(tap: .cghidEventTap) + } + private func inputSource(withID identifier: String) -> TISInputSource? { guard let sources = TISCreateInputSourceList(nil, true)?.takeRetainedValue() as? [TISInputSource] else { diff --git a/Sources/WhisperKeyInput/main.swift b/Sources/WhisperKeyInput/main.swift index 07c212b..a7b3464 100644 --- a/Sources/WhisperKeyInput/main.swift +++ b/Sources/WhisperKeyInput/main.swift @@ -87,6 +87,12 @@ private final class CompositionCoordinator: @unchecked Sendable { @objc(WhisperKeyInputController) final class WhisperKeyInputController: IMKInputController { private var draft = "" + private static let wakeModifiers: NSEvent.ModifierFlags = [ + .command, + .option, + .control, + .shift, + ] override init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) { super.init(server: server, delegate: delegate, client: inputClient) @@ -118,6 +124,16 @@ final class WhisperKeyInputController: IMKInputController { super.inputControllerWillClose() } + override func handle(_ event: NSEvent!, client sender: Any!) -> Bool { + let isWakeEvent = event.type == .keyDown + && event.keyCode == UInt16(kVK_F20) + && event.modifierFlags.contains(Self.wakeModifiers) + if isWakeEvent { + inputLog.notice("wake event consumed") + } + return isWakeEvent + } + func setDraft(_ text: String) { inputLog.notice( "setting marked text for \(self.client().bundleIdentifier(), privacy: .public)"