在OSX中,如何判断哪个键盘产生了NSEvent?
In OSX, how to determine which keyboard generated an NSEvent?
我一直在尝试确定(从事件处理程序中)哪个键盘触发了事件。我一直在使用这两个帖子:
- http://www.cocoabuilder.com/archive/cocoa/229902-which-keyboard-barcode-scanner-did-the-event-come-from.html
- http://www.cocoabuilder.com/archive/cocoa/274586-getting-keyboard-type-carbon-vs-cg.html#274586
在第二篇文章中,作者使用 Carbon 技术成功地分离出了他的键盘,但是使用 Cocoa 尝试同样的技巧失败了。
在我自己的系统上都失败了,可能是因为我的无线键盘也是苹果制造的,所以可能会报告与内置键盘相同的标识符。
在第一篇文章中,有人提出了一种在较低级别(可以区分键盘)监视键盘事件并将此数据存储在队列中,并让事件处理程序检索信息的解决方案。
这看起来有点毛茸茸,所以我只是在这里查看是否有人找到了更好的东西。
这是我的代码,它演示了在事件处理程序中区分键盘的失败:
// compile and run from the commandline with:
// clang -fobjc-arc -framework Cocoa -framework Carbon ./tap_k.m -o tap_k
// sudo ./tap_k
#import <Foundation/Foundation.h>
#import <AppKit/NSEvent.h>
//#import <CarbonEventsCore.h>
#include <Carbon/Carbon.h>
typedef CFMachPortRef EventTap;
// - - - - - - - - - - - - - - - - - - - - -
@interface KeyChanger : NSObject
{
@private
EventTap _eventTap;
CFRunLoopSourceRef _runLoopSource;
CGEventRef _lastEvent;
}
@end
// - - - - - - - - - - - - - - - - - - - - -
CGEventRef _tapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
KeyChanger* listener
);
// - - - - - - - - - - - - - - - - - - - - -
@implementation KeyChanger
- (BOOL)tapEvents
{
if (!_eventTap) {
NSLog(@"Initializing an event tap.");
// kCGHeadInsertEventTap -- new event tap should be inserted before any pre-existing event taps at the same location,
_eventTap = CGEventTapCreate( kCGHIDEventTap, // kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit( kCGEventKeyDown )
| CGEventMaskBit( kCGEventFlagsChanged )
| CGEventMaskBit( NSSystemDefined )
,
(CGEventTapCallBack)_tapCallback,
(__bridge void *)(self));
if (!_eventTap) {
NSLog(@"unable to create event tap. must run as root or "
"add privlidges for assistive devices to this app.");
return NO;
}
}
CGEventTapEnable(_eventTap, TRUE);
return [self isTapActive];
}
- (BOOL)isTapActive
{
return CGEventTapIsEnabled(_eventTap);
}
- (void)listen
{
if( ! _runLoopSource ) {
if( _eventTap ) { // dont use [self tapActive]
NSLog(@"Registering event tap as run loop source.");
_runLoopSource = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, _eventTap, 0 );
// Add to the current run loop.
CFRunLoopAddSource( CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes );
CFRunLoopRun();
}else{
NSLog(@"No Event tap in place! You will need to call listen after tapEvents to get events.");
}
}
}
- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
//NSEventType type = [event type];
EventRef ce = (EventRef)[event eventRef];
if(ce)
{
unsigned kbt;
GetEventParameter(
ce,
kEventParamKeyboardType,
typeUInt32, NULL,
sizeof kbt, NULL,
& kbt
);
NSLog(@"CARBON Keyboard type: %d",kbt);
}
CGEventSourceRef evSrc = CGEventCreateSourceFromEvent( cgEvent );
if(evSrc)
{
unsigned kbt = (NSUInteger) CGEventSourceGetKeyboardType( evSrc );
CFRelease(evSrc);
NSLog(@"COCOA: %d",kbt);
}
//[super sendEvent:anEvent];
//}
NSUInteger modifiers = [event modifierFlags] &
(NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSControlKeyMask);
enum {
kVK_ANSI_3 = 0x14,
};
switch( event.type ) {
case NSFlagsChanged:
NSLog(@"NSFlagsChanged: %d", event.keyCode);
break;
case NSSystemDefined:
NSLog(@"NSSystemDefined: %lx", event.data1);
return NULL;
case kCGEventKeyDown:
NSLog(@"KeyDown: %d", event.keyCode);
break;
default:
NSLog(@"WTF");
}
// TODO: add other cases and do proper handling of case
if (
//[event.characters caseInsensitiveCompare:@"3"] == NSOrderedSame
event.keyCode == kVK_ANSI_3
&& modifiers == NSShiftKeyMask
)
{
NSLog(@"Got SHIFT+3");
event = [NSEvent keyEventWithType: event.type
location: NSZeroPoint
modifierFlags: event.modifierFlags & ! NSShiftKeyMask
timestamp: event.timestamp
windowNumber: event.windowNumber
context: event.context
characters: @"#"
charactersIgnoringModifiers: @"#"
isARepeat: event.isARepeat
keyCode: event.keyCode];
}
_lastEvent = [event CGEvent];
CFRetain(_lastEvent); // must retain the event. will be released by the system
return _lastEvent;
}
- (void)dealloc
{
if( _runLoopSource ) {
CFRunLoopRemoveSource( CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes );
CFRelease( _runLoopSource );
}
if( _eventTap ) {
//kill the event tap
CGEventTapEnable( _eventTap, FALSE );
CFRelease( _eventTap );
}
}
@end
// - - - - - - - - - - - - - - - - - - - - -
CGEventRef _tapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
KeyChanger* listener
)
{
//Do not make the NSEvent here.
//NSEvent will throw an exception if we try to make an event from the tap timout type
@autoreleasepool {
if( type == kCGEventTapDisabledByTimeout ) {
NSLog(@"event tap has timed out, re-enabling tap");
[listener tapEvents];
return nil;
}
if( type != kCGEventTapDisabledByUserInput ) {
return [listener processEvent:event];
}
}
return event;
}
// - - - - - - - - - - - - - - - - - - - - -
int main(int argc, const char * argv[])
{
@autoreleasepool {
KeyChanger* keyChanger = [KeyChanger new];
[keyChanger tapEvents];
[keyChanger listen];//blocking call.
}
return 0;
}
以下是您可以执行的操作的概述:
您设置了一个 IOHIDManager 并设置了输入匹配字典来匹配键盘。
然后 IOHIDManager 将为您提供对所有连接的键盘的引用作为 IOHIDDevices。
最后您可以为 IOHIDDevices 设置输入回调。
现在您可以为每个设备单独输入回调!
设置和使用它有点麻烦,而且它不会像 CGEventTap 那样让您 filter/alter 事件。但这是我所知道的唯一一种监视输入的方法,这样您就可以知道哪个设备导致了哪个输入。
以下是一些起点:
IOHIDManager 文档
IOHIDUsageTables.h 和 IOHIDDeviceKeys.h
- https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-421.6/IOHIDFamily/IOHIDUsageTables.h.auto.html
- https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-1446.40.16/IOHIDFamily/IOHIDDeviceKeys.h.auto.html
- 设备匹配字典需要这些。
对于键盘,您需要像这样声明匹配字典
NSDictionary *matchDict = @{
@(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop),
@(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_Keyboard),
};
(然后通过免费桥接将其转换为 CFDictionaryRef)
(不确定是否正确 - none 已测试)
我一直在尝试确定(从事件处理程序中)哪个键盘触发了事件。我一直在使用这两个帖子:
- http://www.cocoabuilder.com/archive/cocoa/229902-which-keyboard-barcode-scanner-did-the-event-come-from.html
- http://www.cocoabuilder.com/archive/cocoa/274586-getting-keyboard-type-carbon-vs-cg.html#274586
在第二篇文章中,作者使用 Carbon 技术成功地分离出了他的键盘,但是使用 Cocoa 尝试同样的技巧失败了。
在我自己的系统上都失败了,可能是因为我的无线键盘也是苹果制造的,所以可能会报告与内置键盘相同的标识符。
在第一篇文章中,有人提出了一种在较低级别(可以区分键盘)监视键盘事件并将此数据存储在队列中,并让事件处理程序检索信息的解决方案。
这看起来有点毛茸茸,所以我只是在这里查看是否有人找到了更好的东西。
这是我的代码,它演示了在事件处理程序中区分键盘的失败:
// compile and run from the commandline with:
// clang -fobjc-arc -framework Cocoa -framework Carbon ./tap_k.m -o tap_k
// sudo ./tap_k
#import <Foundation/Foundation.h>
#import <AppKit/NSEvent.h>
//#import <CarbonEventsCore.h>
#include <Carbon/Carbon.h>
typedef CFMachPortRef EventTap;
// - - - - - - - - - - - - - - - - - - - - -
@interface KeyChanger : NSObject
{
@private
EventTap _eventTap;
CFRunLoopSourceRef _runLoopSource;
CGEventRef _lastEvent;
}
@end
// - - - - - - - - - - - - - - - - - - - - -
CGEventRef _tapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
KeyChanger* listener
);
// - - - - - - - - - - - - - - - - - - - - -
@implementation KeyChanger
- (BOOL)tapEvents
{
if (!_eventTap) {
NSLog(@"Initializing an event tap.");
// kCGHeadInsertEventTap -- new event tap should be inserted before any pre-existing event taps at the same location,
_eventTap = CGEventTapCreate( kCGHIDEventTap, // kCGSessionEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit( kCGEventKeyDown )
| CGEventMaskBit( kCGEventFlagsChanged )
| CGEventMaskBit( NSSystemDefined )
,
(CGEventTapCallBack)_tapCallback,
(__bridge void *)(self));
if (!_eventTap) {
NSLog(@"unable to create event tap. must run as root or "
"add privlidges for assistive devices to this app.");
return NO;
}
}
CGEventTapEnable(_eventTap, TRUE);
return [self isTapActive];
}
- (BOOL)isTapActive
{
return CGEventTapIsEnabled(_eventTap);
}
- (void)listen
{
if( ! _runLoopSource ) {
if( _eventTap ) { // dont use [self tapActive]
NSLog(@"Registering event tap as run loop source.");
_runLoopSource = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, _eventTap, 0 );
// Add to the current run loop.
CFRunLoopAddSource( CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes );
CFRunLoopRun();
}else{
NSLog(@"No Event tap in place! You will need to call listen after tapEvents to get events.");
}
}
}
- (CGEventRef)processEvent:(CGEventRef)cgEvent
{
NSEvent* event = [NSEvent eventWithCGEvent:cgEvent];
//NSEventType type = [event type];
EventRef ce = (EventRef)[event eventRef];
if(ce)
{
unsigned kbt;
GetEventParameter(
ce,
kEventParamKeyboardType,
typeUInt32, NULL,
sizeof kbt, NULL,
& kbt
);
NSLog(@"CARBON Keyboard type: %d",kbt);
}
CGEventSourceRef evSrc = CGEventCreateSourceFromEvent( cgEvent );
if(evSrc)
{
unsigned kbt = (NSUInteger) CGEventSourceGetKeyboardType( evSrc );
CFRelease(evSrc);
NSLog(@"COCOA: %d",kbt);
}
//[super sendEvent:anEvent];
//}
NSUInteger modifiers = [event modifierFlags] &
(NSCommandKeyMask | NSAlternateKeyMask | NSShiftKeyMask | NSControlKeyMask);
enum {
kVK_ANSI_3 = 0x14,
};
switch( event.type ) {
case NSFlagsChanged:
NSLog(@"NSFlagsChanged: %d", event.keyCode);
break;
case NSSystemDefined:
NSLog(@"NSSystemDefined: %lx", event.data1);
return NULL;
case kCGEventKeyDown:
NSLog(@"KeyDown: %d", event.keyCode);
break;
default:
NSLog(@"WTF");
}
// TODO: add other cases and do proper handling of case
if (
//[event.characters caseInsensitiveCompare:@"3"] == NSOrderedSame
event.keyCode == kVK_ANSI_3
&& modifiers == NSShiftKeyMask
)
{
NSLog(@"Got SHIFT+3");
event = [NSEvent keyEventWithType: event.type
location: NSZeroPoint
modifierFlags: event.modifierFlags & ! NSShiftKeyMask
timestamp: event.timestamp
windowNumber: event.windowNumber
context: event.context
characters: @"#"
charactersIgnoringModifiers: @"#"
isARepeat: event.isARepeat
keyCode: event.keyCode];
}
_lastEvent = [event CGEvent];
CFRetain(_lastEvent); // must retain the event. will be released by the system
return _lastEvent;
}
- (void)dealloc
{
if( _runLoopSource ) {
CFRunLoopRemoveSource( CFRunLoopGetCurrent(), _runLoopSource, kCFRunLoopCommonModes );
CFRelease( _runLoopSource );
}
if( _eventTap ) {
//kill the event tap
CGEventTapEnable( _eventTap, FALSE );
CFRelease( _eventTap );
}
}
@end
// - - - - - - - - - - - - - - - - - - - - -
CGEventRef _tapCallback(
CGEventTapProxy proxy,
CGEventType type,
CGEventRef event,
KeyChanger* listener
)
{
//Do not make the NSEvent here.
//NSEvent will throw an exception if we try to make an event from the tap timout type
@autoreleasepool {
if( type == kCGEventTapDisabledByTimeout ) {
NSLog(@"event tap has timed out, re-enabling tap");
[listener tapEvents];
return nil;
}
if( type != kCGEventTapDisabledByUserInput ) {
return [listener processEvent:event];
}
}
return event;
}
// - - - - - - - - - - - - - - - - - - - - -
int main(int argc, const char * argv[])
{
@autoreleasepool {
KeyChanger* keyChanger = [KeyChanger new];
[keyChanger tapEvents];
[keyChanger listen];//blocking call.
}
return 0;
}
以下是您可以执行的操作的概述:
您设置了一个 IOHIDManager 并设置了输入匹配字典来匹配键盘。
然后 IOHIDManager 将为您提供对所有连接的键盘的引用作为 IOHIDDevices。
最后您可以为 IOHIDDevices 设置输入回调。 现在您可以为每个设备单独输入回调!
设置和使用它有点麻烦,而且它不会像 CGEventTap 那样让您 filter/alter 事件。但这是我所知道的唯一一种监视输入的方法,这样您就可以知道哪个设备导致了哪个输入。
以下是一些起点:
IOHIDManager 文档
IOHIDUsageTables.h 和 IOHIDDeviceKeys.h
- https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-421.6/IOHIDFamily/IOHIDUsageTables.h.auto.html
- https://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-1446.40.16/IOHIDFamily/IOHIDDeviceKeys.h.auto.html
- 设备匹配字典需要这些。
对于键盘,您需要像这样声明匹配字典
NSDictionary *matchDict = @{
@(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop),
@(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_Keyboard),
};
(然后通过免费桥接将其转换为 CFDictionaryRef) (不确定是否正确 - none 已测试)