macOS底层输入IOHID
macOS底层输入IOHID

macOS底层输入IOHID

OHID 是 macOS(和 iOS)底层输入系统的一部分,属于 I/O Kit(驱动层框架),全称是:IOHID = I/O Human Interface Device。

它是一套用于处理 人类输入设备(Human Interface Devices) 的驱动接口,比如:键盘、鼠标、触控板、游戏手柄、触摸屏或自定义输入设备(如扫描枪)。

基本作用

1、底层:比 NSEvent / CGEvent 更低级,直接处理物理输入设备数据;

2、实时:IOHID 能在输入发生的第一时间收到事件(适用于游戏、低延迟设备);

3、权限要求高:很多功能需要辅助功能权限;

4、可绕过 App 层限制:比如可拦截被系统或其他 App 吞掉的组合键;

5、使用复杂:是 C 接口,必须通过复杂的匹配字典、RunLoop 注册等;

6、风险高:不当使用可能影响系统稳定性;macOS 安全策略越来越限制它的使用。

使用示例

1、监听快捷键:

let manager = IOHIDManagerCreate(kCFAllocatorDefault, IOOptionBits(kIOHIDOptionsTypeNone))

let matchingDict: [String: Any] = [
    kIOHIDDeviceUsagePageKey as String: kHIDPage_GenericDesktop,
    kIOHIDDeviceUsageKey as String: kHIDUsage_GD_Keyboard
]

IOHIDManagerSetDeviceMatching(manager!, matchingDict as CFDictionary)

let callback: IOHIDValueCallback = { context, result, sender, value in
    let element = IOHIDValueGetElement(value)
    let usagePage = IOHIDElementGetUsagePage(element)
    let usage = IOHIDElementGetUsage(element)
    let pressed = IOHIDValueGetIntegerValue(value) != 0

    let this = Unmanaged<HIDKeyboardMonitor>.fromOpaque(context!).takeUnretainedValue()
    let isOptionPressed = false
    // Option 键的 usage 是 0x3A(左 Option)
    if usage == 0x3A {
        isOptionPressed = pressed
    }

    // A 键的 usage 是 0x04
    if usage == 0x04 && pressed && isOptionPressed {
        print("Option + A detected!")
    }
}

IOHIDManagerRegisterInputValueCallback(manager!, callback, UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()))
IOHIDManagerScheduleWithRunLoop(manager!, CFRunLoopGetCurrent(), CFRunLoopMode.defaultMode.rawValue)
IOHIDManagerOpen(manager!, IOOptionBits(kIOHIDOptionsTypeNone))

这相当于直接监听设备底层输入事件,比 RegisterEventHotKey 优先级更高,也能捕获注册不到的组合键。

权限问题

macOS 对 IOHID 使用非常严格:

1、macOS 10.15+:需要辅助功能权限;

2、部分功能需关闭 App Sandboxing(否则无法监听全局事件);

3、沙盒 App 无法使用 IOHIDManagerCreate 监听系统设备。

总结

IOHID 是 macOS 输入事件的“驱动级入口”,权限高、性能强,但使用复杂且受限制。

实际上IOHID在当前的Swift版本中,比较困难,以下是难点:

1、需要用户授权“输入监控”权限:

否则会提示:

0x1003b4301: TCC deny IOHIDDeviceOpen

这个报错表示应用尝试访问 HID 设备(键盘、鼠标等),但系统拒绝了,因为没有获得用户授权的“输入监控”权限(Input Monitoring)。

2、需要关闭App Sandbox权限,如果不关闭,Xcode会提示:

IOServiceOpen failed: 0xe00002e2

问题原因为,程序没有权限访问某个 IOKit 服务(比如 IOHIDSystem、IOHIDDevice),在“App Sandbox 没有关闭”的前提下,即使赋予输入监控权限,Sandbox 依然会阻止访问 IOHID 低层接口。

3、实际测试发现,TCC权限系统拒绝IOHIDDeviceOpen的访问,即使添加了辅助功能权限、输入监控权限、关闭了App Sandbox。

仍然无法访问物理设备级HID权限,这个权限受 macOS 更深层的保护机制限制(TCC + SIP),尤其是在沙盒外运行的普通 app,macOS 会视其访问硬件键盘为高危操作。

可能的原因为:macOS 的 IOHIDManager 属于低层次的 I/O 框架,设计用于驱动层或系统扩展开发者(如键盘驱动、键盘拦截器),而不是用户空间应用。如果不是系统扩展、内核扩展(KEXT) 或 System Extension(DriverKit) 类型的 App,就会被限制访问。

如果是一个全局热键工具、辅助设备处理器、或模拟键盘输入的底层工具,IOHID 是必学接口。但如果只是做普通 App,优先选 RegisterEventHotKey 或 CGEventTap。

如果想要监听快捷键,可以使用以下三种方式进行替代:

1、NSEvent.addGlobalMonitorForEvents(需要辅助权限)

NSEvent.addGlobalMonitorForEvents(matching: [.keyDown]) { event in
    if event.modifierFlags.contains(.option) && event.charactersIgnoringModifiers == "a" {
        print("Option + A pressed")
    }
}

优点:适合监听键盘事件,行为符合用户预期;

权限要求:必须加入辅助功能权限;

无需关闭 Sandbox。

2、使用 Carbon RegisterEventHotKey(系统层级快捷键注册)

优点:可注册系统级快捷键,抢占式;

权限要求:无辅助权限也可以使用;

优先级比其他监听方式要低。

3、macOS监听/拦截输入事件CGEventTap

优点:低层级机制,监听或拦截系统中的输入事件。

权限要求:需要启用“按键接收”权限。

因为属于Core Graphics框架,API从C和Objective-C演变过来,代码涉及C语言,使用比较复杂。

相关文章

1、macOS注册全局快捷键RegisterEventHotKey :https://fangjunyu.com/2025/07/26/macos%e6%b3%a8%e5%86%8c%e5%85%a8%e5%b1%80%e5%bf%ab%e6%8d%b7%e9%94%aeregistereventhotkey/

2、macOS监听/拦截输入事件CGEventTap:https://fangjunyu.com/2025/07/23/macos%e7%9b%91%e5%90%ac-%e6%8b%a6%e6%88%aa%e8%be%93%e5%85%a5%e4%ba%8b%e4%bb%b6cgeventtap/

3、macOS模拟事件CGEvent:https://fangjunyu.com/2025/07/22/macos%e6%a8%a1%e6%8b%9f%e4%ba%8b%e4%bb%b6cgevent/

4、Apple安全机制TCC:https://fangjunyu.com/2025/07/26/apple%e5%ae%89%e5%85%a8%e6%9c%ba%e5%88%b6tcc/

   

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开发者。

欢迎加入我们的 微信交流群QQ交流群,交流更多精彩内容!
微信交流群二维码 QQ交流群二维码

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注