import Cocoa import CoreGraphics /// 全局 Fn / Globe 键监听器 final class KeyMonitor { // Fn 键标志:CGEventFlags.maskSecondaryFn = 0x00800000 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) | (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) } // 尝试 HID 级 tap(能捕获被系统拦截的 Fn 键) var tap = CGEvent.tapCreate( tap: .cghidEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(eventMask), callback: callback, userInfo: Unmanaged.passUnretained(self).toOpaque() ) if tap == nil { // 回退到会话级 tap 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? { let flags = event.flags.rawValue let fnInFlags = (flags & Self.fnMask) != 0 switch type { case .flagsChanged: if fnInFlags && !isFnPressed { print("[KeyMonitor] ⚡️ Fn DOWN (flags=0x\(String(flags, radix: 16)))") return handleFnDown() } if !fnInFlags && isFnPressed { print("[KeyMonitor] ⚡️ Fn UP (flags=0x\(String(flags, radix: 16)))") return handleFnUp() } return Unmanaged.passUnretained(event) case .keyDown, .keyUp: let keyCode = event.getIntegerValueField(.keyboardEventKeycode) if keyCode == 63 && fnInFlags { return handleFnDown() } return Unmanaged.passUnretained(event) default: return Unmanaged.passUnretained(event) } } private func handleFnDown() -> Unmanaged? { if !isFnPressed { isFnPressed = true DispatchQueue.main.async { [weak self] in self?.onFnDown?() } } return nil } private func handleFnUp() -> Unmanaged? { if isFnPressed { isFnPressed = false DispatchQueue.main.async { [weak self] in self?.onFnUp?() } } return nil } }