如何将NSEvent标记为已处理?全局键盘事件监听后阻止重复处理
Great question—let’s clear up a common misconception first, then dive into the working solution.
Why addGlobalMonitorForEvents Can’t Block Events
The NSEvent.addGlobalMonitorForEvents(matching: .keyDown) API you’re using is a passive observer only. It lets you listen to key events after they’ve already been sent to the active application (like your text editor). There’s no way to "mark the event as handled" here—by the time your callback runs, the app has already received the input. This tool is meant for observing, not intercepting.
The Right Tool: Core Graphics Event Taps
To block events before they reach other apps, you need to use Core Graphics Event Taps. This low-level system API lets you intercept events at the session level, before they’re dispatched to any app. Here’s how to implement it in Swift:
1. Create the Event Tap Callback
This function will decide whether to let the event pass through or block it. Returning nil tells the system to discard the event entirely.
import Cocoa import CoreGraphics func eventTapCallback( proxy: CGEventTapProxy, type: CGEventType, event: CGEvent, refcon: UnsafeMutableRawPointer? ) -> Unmanaged<CGEvent>? { // Only handle key down events guard type == .keyDown else { // Pass non-key events through to apps return Unmanaged.passRetained(event) } // Get the key code of the pressed key (example: block the 'A' key, code 0) let keyCode = event.getIntegerValueField(.keyboardEventKeycode) if keyCode == 0 { // Block this event: don't pass it to any app return nil } // Allow other key events to proceed return Unmanaged.passRetained(event) }
2. Set Up and Activate the Event Tap
Next, we’ll create the tap, hook it into the system’s event stream, and start processing events.
func setupGlobalKeyInterceptor() { // Define which events we want to tap: only key down events let eventMask = 1 << CGEventType.keyDown.rawValue // Create the event tap guard let eventTap = CGEventTapCreate( .cgsessionEventTap, // Tap all apps in the current user session .headInsertEventTap, // Insert at the start of the event stream to intercept first .defaultTap, eventMask, eventTapCallback, nil ) else { print("Failed to create event tap! Make sure your app has Accessibility permissions enabled.") return } // Attach the tap to the run loop so it can process events let runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0) CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes) // Turn on the event tap CGEventTapEnable(eventTap, true) // Keep the run loop running (required for the tap to work) CFRunLoopRun() } // Start the interceptor when your app launches setupGlobalKeyInterceptor()
3. Critical: Enable Accessibility Permissions
For this to work, your app must have Accessibility permissions enabled. Here’s how to set this up:
- Open System Settings > Privacy & Security > Accessibility
- Toggle on your app in the list
- If your app isn’t listed, you may need to run it first, then check again.
You can also programmatically check if permissions are enabled using AXIsProcessTrusted() and prompt the user if they’re not.
Key Tips for Success
- Key Codes: To block other keys, replace the
keyCode == 0check with the code for your target key. You can find macOS key codes using tools likehidutil(runhidutil property --get "UserKeyMapping"in Terminal) or Apple’s official documentation. - Run Loop: The event tap relies on the run loop staying active. If your app exits the run loop (e.g., a command-line tool finishes execution), the tap will stop working.
- Scope: The
.cgsessionEventTapscope affects all apps in your user session. If you only want to intercept events for your own app, use.cgAnnotatedSessionEventTapinstead. - System Compatibility: Event taps work on macOS 10.4+, but newer versions (10.14+) have stricter permission requirements, so make sure to handle that in your app.
Why This Works
Unlike the global monitor, the event tap intercepts events before they’re sent to the target application. When you return nil from the callback, the system discards the event—so your text editor (or any other app) never even sees it.
内容的提问来源于stack exchange,提问作者Sergei Moskvin

