VoiceInput/Sources/Core/KeyMonitor.swift

107 lines
3.3 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Cocoa
import CoreGraphics
/// Fn
final class KeyMonitor {
// Fn
private static let fnMask: UInt64 = 0x00800000
private var eventTap: CFMachPort?
private var runLoopSource: CFRunLoopSource?
private var isFnPressed = false
var onFnDown: (() -> Void)?
var onFnUp: (() -> Void)?
func start() -> Bool {
guard eventTap == nil else { return true }
guard CGPreflightListenEventAccess() else {
CGRequestListenEventAccess()
print("[KeyMonitor] ❌ Accessibility permission not granted")
return false
}
let eventMask = (1 << CGEventType.flagsChanged.rawValue)
let callback: CGEventTapCallBack = { (proxy, type, event, refcon) -> Unmanaged<CGEvent>? in
let monitor = Unmanaged<KeyMonitor>.fromOpaque(refcon!).takeUnretainedValue()
return monitor.handleEvent(proxy: proxy, type: type, event: event)
}
var tap = CGEvent.tapCreate(
tap: .cghidEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(eventMask),
callback: callback,
userInfo: Unmanaged.passUnretained(self).toOpaque()
)
if tap == nil {
print("[KeyMonitor] ⚠️ HID tap failed, falling back to session tap")
tap = CGEvent.tapCreate(
tap: .cgSessionEventTap,
place: .headInsertEventTap,
options: .defaultTap,
eventsOfInterest: CGEventMask(eventMask),
callback: callback,
userInfo: Unmanaged.passUnretained(self).toOpaque()
)
}
guard let tap = tap else {
print("[KeyMonitor] ❌ Failed to create event tap")
return false
}
eventTap = tap
runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
CGEvent.tapEnable(tap: tap, enable: true)
print("[KeyMonitor] ✅ Started")
return true
}
func stop() {
guard let tap = eventTap else { return }
CGEvent.tapEnable(tap: tap, enable: false)
if let source = runLoopSource {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, .commonModes)
}
eventTap = nil
runLoopSource = nil
print("[KeyMonitor] Stopped")
}
private func handleEvent(proxy: CGEventTapProxy, type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
guard type == .flagsChanged else {
return Unmanaged.passUnretained(event)
}
let flags = event.flags.rawValue
let fnActive = (flags & Self.fnMask) != 0
if fnActive && !isFnPressed {
isFnPressed = true
print("[KeyMonitor] ⚡️ Fn DOWN")
DispatchQueue.main.async { [weak self] in
self?.onFnDown?()
}
return nil
}
if !fnActive && isFnPressed {
isFnPressed = false
print("[KeyMonitor] ⚡️ Fn UP")
DispatchQueue.main.async { [weak self] in
self?.onFnUp?()
}
return nil
}
return Unmanaged.passUnretained(event)
}
}