import Cocoa import CoreGraphics /// 全局热键监听器:Fn 键(语音输入)+ Control 键(AI 助手) final class KeyMonitor { // Fn 键:NX_SECONDARYFNMASK private static let fnMask: UInt64 = 0x00800000 // Control 键:NX_CONTROLMASK private static let controlMask: UInt64 = 0x00001000 private var eventTap: CFMachPort? private var runLoopSource: CFRunLoopSource? private var isFnPressed = false private var isControlPressed = false var onFnDown: (() -> Void)? var onFnUp: (() -> Void)? var onControlDown: (() -> Void)? var onControlUp: (() -> 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) | (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue) let callback: CGEventTapCallBack = { (proxy, type, event, refcon) -> Unmanaged? in let monitor = Unmanaged.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 (Fn + Control)") 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? { let flags = event.flags.rawValue _ = event.getIntegerValueField(.keyboardEventKeycode) let fnInFlags = (flags & Self.fnMask) != 0 let controlInFlags = (flags & Self.controlMask) != 0 switch type { case .flagsChanged: // Fn 键检测 if fnInFlags && !isFnPressed { isFnPressed = true DispatchQueue.main.async { [weak self] in self?.onFnDown?() } return nil } if !fnInFlags && isFnPressed { isFnPressed = false DispatchQueue.main.async { [weak self] in self?.onFnUp?() } return nil } // Control 键检测 if controlInFlags && !isControlPressed { isControlPressed = true DispatchQueue.main.async { [weak self] in self?.onControlDown?() } return nil } if !controlInFlags && isControlPressed { isControlPressed = false DispatchQueue.main.async { [weak self] in self?.onControlUp?() } return nil } return Unmanaged.passUnretained(event) default: return Unmanaged.passUnretained(event) } } }