我的目标是使用 IOHID 阻止击键到达操作系统(由于其他原因无法使用 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
此代码运行并设法从操作系统全局查看和打印所有击键,但似乎 kIOHIDOptionsTypeSeizeDevice
被忽略,因为击键仍在传递到 macOS。
编辑:
将 IOReturn result =
添加到代码中会暴露错误 -536870207
,该错误会转换为 kIOReturnNotPrivileged
。然后我将 Xcode 方案更改为 root 并且能够阻止键盘按键。
这引出了下一个问题,如何将此代码添加到显然不以 root 权限运行的开发者 ID 应用程序中?
最佳答案
很高兴我们在评论中将您的 HID 问题追踪到权限问题。
要在生产/部署中以 root 身份运行代码,您需要将一个单独的工具设置为 Launch Daemon ,并将其配置为在您的主应用向其发送 IPC(通常是 XPC)消息时按需启动。
首先,您有 2 个主要选项来设置启动守护进程:
- 将守护程序二进制文件嵌入到您的 .app 中,并从应用代码中调用
SMJobBless
将其安装到系统中。这将要求用户输入管理员密码。 - 一个安装程序 .pkg,将您的守护程序二进制文件放置在固定位置(例如在
/Library/Application Support/YourAppName/
下方),并放置相应的 launchd plist在/Library/LaunchDaemons
中。
请注意,在设置启动守护程序时,您需要非常小心,以避免打开安全漏洞。 This series of articles是关于该做什么和该避免什么的很好的深入指南。
另请注意,App Store 不允许使用 SMJobBless
,因此此类功能对于在那里分发的应用来说根本不可能。
关于macos - 使用 kIOHIDOptionsTypeSeizeDevice 时,击键不会被阻止,并且仍会传递到操作系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70673002/