OSX:检测系统范围的keyDown事件?

问题描述 投票:56回答:4

我正在为Mac OSX开发一个打字导师应用程序,即使应用程序没有被关注,也需要将键击转发给它。

有没有办法让系统向应用程序转发击键,可能是通过NSDistributedNotificationCenter?我用谷歌搜索了自己,并且找不到答案......

编辑:下面的示例代码。

感谢@NSGod指出我正确的方向 - 我最后使用global events monitor方法添加了一个addGlobalMonitorForEventsMatchingMask:handler:,它工作得很漂亮。为了完整起见,我的实现看起来像这样:

// register for keys throughout the device...
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                       handler:^(NSEvent *event){

    NSString *chars = [[event characters] lowercaseString];
    unichar character = [chars characterAtIndex:0];

    NSLog(@"keydown globally! Which key? This key: %c", character);

}];

对我来说,棘手的部分是使用块,所以我会给出一些描述,以防它帮助任何人:

关于上面代码的注意事项是它是NSEvent上的所有单个方法调用。该块作为参数直接提供给函数。你可以认为它有点像内联委托方法。仅仅因为这需要一段时间才能让我沉沦,我将在这里一步一步地完成它:

[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask

这第一位没问题。您正在NSEvent上调用一个类方法,并告诉它您要监视哪个事件,在本例中为NSKeyDownMask。一个list of masks for supported event types可以找到here

现在,我们来到棘手的部分:处理程序,它需要一个块:

handler:^(NSEvent *event){

我花了一些编译错误才能做到这一点,但是(谢谢Apple)他们是非常有建设性的错误信息。首先要注意的是克拉^。这标志着块的开始。在那之后,在括号内,

NSEvent *event

其中声明了您将在块中使用的变量来捕获事件。你可以打电话给它

NSEvent *someCustomNameForAnEvent

没关系,你只是在块内使用该名称。然后,这就是它的全部内容。确保关闭大括号和括号以完成方法调用:

}];

而且你已经完成了!这真的是一种“单线”。在您的应用程序中执行此调用的位置无关紧要 - 我在AppDelegate的applicationDidFinishLaunching方法中执行此操作。然后,在块中,您可以从应用程序中调用其他方法。

macos keyboard keydown nsnotificationcenter
4个回答
24
投票

如果您对OS X 10.6+的最低要求没有问题,并且只需对事件流进行“只读”访问即可,您可以在Cocoa:Cocoa Event-Handling Guide: Monitoring Events中安装全局事件监视器。

如果您需要支持OS X 10.5及更早版本,并且只读访问权限可以,并且不介意使用Carbon事件管理器,您基本上可以使用GetEventMonitorTarget()执行Carbon等效操作。 (你会很难找到关于该方法的任何(官方)文档)。我相信,该API首先在OS X 10.3中可用。

如果您需要对事件流的读写访问权限,那么您将需要查看属于ApplicationServices> CoreGraphics的一个稍低级别的API:CGEventTapCreate()和朋友。这是第一次在10.4中提供。

请注意,所有3种方法都要求用户在“系统首选项”>“通用访问”首选项窗格中启用“启用辅助设备访问”(至少对于键事件)。


14
投票

我发布了适合我案例的代码。

我在应用程序启动后添加了全局事件处理程序。我的快捷方式使ctrl + alt + cmd + T打开我的应用程序。

- (void) applicationWillFinishLaunching:(NSNotification *)aNotification
{
    // Register global key handler, passing a block as a callback function
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                           handler:^(NSEvent *event){

        // Activate app when pressing cmd+ctrl+alt+T
        if([event modifierFlags] == 1835305 && [[event charactersIgnoringModifiers] compare:@"t"] == 0) {

              [NSApp activateIgnoringOtherApps:YES];
        }
    }];

}

4
投票

我发现这个问题是,由另一个应用程序在全球注册的任何密钥都不会是正确的...或者至少在我的情况下,也许我做错了。

如果您的程序需要显示所有键,例如“Command-Shift-3”,那么它将不会看到它显示它...因为它被操作系统占用。

还是有人想出来的?我很想知道......


3
投票

正如NSGod已经指出的那样你也可以使用CoreGraphics。

在你的班级(例如in -init):

CFRunLoopRef runloop = (CFRunLoopRef)CFRunLoopGetCurrent();

CGEventMask interestedEvents = NSKeyDown;
CFMachPortRef eventTap = CGEventTapCreate(kCGSessionEventTap, kCGHeadInsertEventTap, 
                                        0, interestedEvents, myCGEventCallback, self);
// by passing self as last argument, you can later send events to this class instance

CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, 
                                                           eventTap, 0);
CFRunLoopAddSource((CFRunLoopRef)runloop, source, kCFRunLoopCommonModes);
CFRunLoopRun();

在类之外,但在同一个.m文件中:

CGEventRef myCGEventCallback(CGEventTapProxy proxy, 
                             CGEventType type, 
                             CGEventRef event, 
                             void *refcon)
{

    if(type == NX_KEYDOWN)
    {
       // we convert our event into plain unicode
       UniChar myUnichar[2];
       UniCharCount actualLength;
       UniCharCount outputLength = 1;
       CGEventKeyboardGetUnicodeString(event, outputLength, &actualLength, myUnichar);

       // do something with the key

       NSLog(@"Character: %c", *myUnichar);
       NSLog(@"Int Value: %i", *myUnichar);

       // you can now also call your class instance with refcon
       [(id)refcon sendUniChar:*myUnichar];
   }

   // send event to next application
   return event;
}
© www.soinside.com 2019 - 2024. All rights reserved.