You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Mac菜单栏应用全局禁用键盘输入的技术实现问询

解决方案:全局禁用Mac键盘输入(保留触控板/鼠标)

你的现有代码问题

你当前的代码仅创建并发送了一个虚拟键事件,并未对系统的键盘输入进行拦截,因此无法实现禁用效果。要全局禁用键盘,需通过CGEventTap拦截所有键盘事件并直接丢弃。

正确实现步骤

1. 申请辅助功能权限

Mac系统要求应用拥有辅助功能权限才能监听/拦截全局事件。你需要:

  • 在应用的Info.plist中添加NSAccessibilityUsageDescription键,填写权限用途说明(例如"需要权限以全局禁用键盘输入")
  • 引导用户前往系统偏好设置→「安全性与隐私」→「隐私」→「辅助功能」,启用你的应用

2. 实现CGEventTap拦截键盘事件

以下是完整Swift代码示例,实现全局禁用/启用键盘的功能:

import Cocoa

class KeyboardDisabler: NSObject {
    private var eventTap: CFMachPort?
    private var runLoopSource: CFRunLoopSource?
    private var isKeyboardDisabled = false
    
    func toggleKeyboard() {
        isKeyboardDisabled.toggle()
        
        if isKeyboardDisabled {
            setupEventTap()
            NSLog("Keyboard Disabled")
        } else {
            removeEventTap()
            NSLog("Keyboard Enabled")
        }
    }
    
    private func setupEventTap() {
        guard eventTap == nil else { return }
        
        // 监听键盘按下/抬起事件
        let eventMask = (1 << CGEventType.keyDown.rawValue) | (1 << CGEventType.keyUp.rawValue)
        eventTap = CGEvent.tapCreate(
            tap: .cgSessionEventTap,
            place: .headInsertEventTap,
            options: .defaultTap,
            eventsOfInterest: CGEventMask(eventMask),
            callback: { proxy, type, event, refcon in
                let disabler = unsafeBitCast(refcon, to: KeyboardDisabler.self)
                // 禁用状态下返回nil,直接丢弃键盘事件
                return disabler.isKeyboardDisabled ? nil : event
            },
            userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
        )
        
        guard let eventTap = eventTap else {
            NSLog("创建事件拦截失败,请检查辅助功能权限")
            return
        }
        
        // 将事件tap加入RunLoop,持续监听
        runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)
        if let runLoopSource = runLoopSource {
            CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
        }
    }
    
    private func removeEventTap() {
        if let runLoopSource = runLoopSource {
            CFRunLoopRemoveSource(CFRunLoopGetCurrent(), runLoopSource, .commonModes)
            self.runLoopSource = nil
        }
        
        if let eventTap = eventTap {
            CFMachPortInvalidate(eventTap)
            self.eventTap = nil
        }
    }
    
    deinit {
        removeEventTap()
    }
}

// 使用示例(在菜单点击事件中调用)
// let keyboardDisabler = KeyboardDisabler()
// @objc func toggleKeyboardAction() {
//     keyboardDisabler.toggleKeyboard()
// }

3. 关键说明

  • 事件拦截逻辑:在CGEventTap的回调中,当isKeyboardDisabled为true时返回nil,系统会直接丢弃该键盘事件,不会传递给任何应用处理。
  • 权限处理:如果CGEvent.tapCreate返回nil,几乎都是因为缺少辅助功能权限,需提示用户开启。
  • 资源清理:在应用退出或不再需要拦截时,必须移除eventTap并清理RunLoop资源,避免内存泄漏。

额外注意事项

  • 部分系统级快捷键(如Cmd+Q、Cmd+Tab)可能无法被完全拦截,这类事件属于系统更高层级的处理流程。若需要拦截此类快捷键,需额外处理,但常规场景下上述代码已能覆盖绝大多数键盘输入。
  • 菜单栏应用默认后台运行,RunLoop设置需确保事件监听持续生效。

内容的提问来源于stack exchange,提问作者Craig Larkin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 21:31:07