【问题标题】:OS X: Detect system-wide keyDown events?OS X:检测系统范围的 keyDown 事件?
【发布时间】:2011-06-12 18:03:06
【问题描述】:

我正在为 Mac OS X 开发一个打字指导应用程序,即使应用程序不在焦点上,也需要将击键转发给它。

有没有办法让系统将击键转发到应用程序,可能通过 NSDistributedNotificationCenter?我用谷歌搜索自己很傻,一直找不到答案......

编辑: 下面的示例代码

感谢@NSGod 为我指明了正确的方向——我最终使用addGlobalMonitorForEventsMatchingMask:handler: 方法添加了global events monitor,效果很好。为了完整起见,我的实现如下所示:

// 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 方法中执行此操作。然后,在该块中,您可以从您的应用程序中调用其他方法。

【问题讨论】:

  • 嘿 @Pirrpli 非常感谢您为我解释块语法。真的对那些学习有帮助!顺便说一句,效果很好:)
  • 没问题。 ;) 当别人为我这样做时,我总是心存感激,所以我会尽力提供帮助。
  • 当我尝试这个代码块时,我得到错误:Incompatible block pointer types sending 'void (^)(struct NSEvent *)' to parameter of type of void (^)(NSEvent *)' in the declaration - @Pirrpli,我忘了添加什么吗?您是否必须包含一些特殊的东西来支持块语法? Screenshot
  • 我能够通过仔细阅读错误(duh)并从语句中删除“struct”来解决这个问题。
  • 请到“系统偏好设置 -> 安全和隐私 -> 隐私 -> 可访问性”检查 Xcode 是否启用,以防它不适用于任何人。

标签: macos keyboard keydown nsnotificationcenter


【解决方案1】:

如果您对 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:CGEventTapCreate() 和朋友的稍低级别的 API。这在 10.4 中首次可用。

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

【讨论】:

  • 太棒了,这看起来很棒!我看看明天能不能把事情搞好,然后接受!一个问题:有没有一种方法可以测试用户是否启用了访问权限,例如允许我提示他们这样做吗?
  • 您可以使用苹果脚本来执行此操作。试试下面的脚本。如果 isUIScriptingEnabled = false 则告诉应用程序“系统事件”将 isUIScriptingEnabled 设置为启用的 UI 元素,然后告诉应用程序“系统偏好设置”激活将当前窗格设置为窗格“com.apple.preference.universalaccess”显示对话框“请选择\”启用访问辅助设备\“复选框并重新启动”返回结束告诉结束如果
【解决方案2】:

我正在发布适用于我的案例的代码。

我在应用启动后添加全局事件处理程序。我的快捷方式使 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];
        }
    }];

}

【讨论】:

    【解决方案3】:

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

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

    或者有人发现了吗?我很想知道...

    【讨论】:

    • 您找到解决方案了吗?我也对它很感兴趣。
    【解决方案4】:

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

    在你的班级中(例如在 -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;
    }
    

    【讨论】:

    • 感谢您的详细帖子!效果很好。
    • 调用[NSEvent eventWithCGEvent:event] 来获得一个NSEvent,它有更好的API 来处理ObjC 代码。在 ARC 中,您需要桥接。示例:[(__bridge MyClass *) refcon myHandlerInstanceMethod:e];
    猜你喜欢
    • 2017-07-30
    • 2016-11-12
    • 1970-01-01
    • 1970-01-01
    • 2012-07-28
    • 1970-01-01
    • 2011-08-31
    • 2016-11-12
    • 2021-04-28
    相关资源
    最近更新 更多