使用 kIOHIDOptionsTypeSeizeDevice 时不会阻止击键,并且仍会传递给 OS

Keystrokes are not blocked when using kIOHIDOptionsTypeSeizeDevice and are still passed to the OS

我的目标是使用 IOHID 阻止击键到达 OS(由于其他原因不能使用 CGEvent)。根据 kIOHIDOptionsTypeSeizeDevice 的文档:

Used to open exclusive communication with the device. This will prevent the system and other clients from receiving events from the device.

#import "TestKeys.h"
#import <IOKit/hid/IOHIDManager.h>
#import <IOKit/hid/IOHIDUsageTables.h>

@implementation TestKeys

#define KEYS 2

static void Handle_InputCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDValueRef value)
{
    IOHIDElementRef elem = IOHIDValueGetElement(value);

    uint16_t scancode = IOHIDElementGetUsage(elem);
    
    if (scancode < 4 || scancode > 231) {
        return;
    }
    
    NSLog(@"Key event received: %d", scancode);
}

static void Handle_DeviceMatchingCallback(void * inContext, IOReturn inResult, void * inSender, IOHIDDeviceRef inIOHIDDeviceRef)
{
    NSLog(@"Connected");
}

static void Handle_RemovalCallback(void * inContext, IOReturn inResult, void * inSender, IOHIDDeviceRef inIOHIDDeviceRef)
{
    NSLog(@"Removed");
}

-(void)start
{
    IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone);
    
    if (CFGetTypeID(manager) != IOHIDManagerGetTypeID()) {
        exit(1);
    }

    int usagePage = kHIDPage_GenericDesktop;
    int usage = kHIDUsage_GD_Keyboard;
    
    CFStringRef keys[KEYS] = {
        CFSTR(kIOHIDDeviceUsagePageKey),
        CFSTR(kIOHIDDeviceUsageKey),
    };
    
    CFNumberRef values[KEYS] = {
        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usagePage),
        CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usage),
    };
    
    CFDictionaryRef matchingDict = CFDictionaryCreate(kCFAllocatorDefault,
                                                      (const void **) keys, (const void **) values, KEYS,
                                                      &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    for (int i=0; i<KEYS; i++) {
        CFRelease(keys[i]);
        CFRelease(values[i]);
    }
    
    IOHIDManagerSetDeviceMatching(manager, matchingDict);
    CFRelease(matchingDict);
    
    IOHIDManagerRegisterDeviceMatchingCallback(manager, Handle_DeviceMatchingCallback, NULL);
    IOHIDManagerRegisterDeviceRemovalCallback(manager, Handle_RemovalCallback, NULL);
    IOHIDManagerRegisterInputValueCallback(manager, Handle_InputCallback, NULL);
    
    IOHIDManagerOpen(manager, kIOHIDOptionsTypeSeizeDevice);

    IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
}
@end

此代码 运行s 并设法从 OS 全局查看和打印所有击键,但似乎 kIOHIDOptionsTypeSeizeDevice 被忽略,因为击键仍在传递到 mac OS.

编辑:IOReturn result = 添加到代码中会暴露错误 -536870207,该错误会转换为 kIOReturnNotPrivileged。然后我将 Xcode 方案更改为 root 并且能够阻止键盘键。

这引出了下一个问题,我如何将此代码添加到显然 运行 没有 root 权限的 Developer ID 应用程序?

很高兴我们将您的 HID 问题追踪到评论中的权限问题。

要在 production/deployment 中以 root 身份 运行 编码,您需要将单独的工具设置为 Launch Daemon,并将其配置为在您的主程序时按需启动应用向它发送一条 IPC(通常是 XPC)消息。

您有 2 个主要选项来首先设置启动守护程序:

  • 在您的 .app 中嵌入守护程序二进制文件,并从您的应用程序代码中调用 SMJobBless 以将其安装到系统中。这将要求用户输入管理员密码。
  • 一个安装程序 .pkg,它将您的守护程序二进制文件放置在某个固定位置(例如 /Library/Application Support/YourAppName/ 下方)并将相应的 launchd plist 放置在 /Library/LaunchDaemons.

请注意,在设置启动守护程序时,您需要相当小心避免打开安全漏洞。 This series of articles 是关于该做什么和该避免什么的很好的深入指南。

另请注意,SMJobBless 不允许在 App Store 上使用,因此此类功能对于在那里分发的应用程序来说根本不可能。