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? 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") 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? { 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) } }