如何监测全局修饰键状态(在任何应用程序中)?

11
我在我的Cocoa项目中使用了一些Carbon代码来处理其他应用程序的全局键盘事件(快捷方式)。目前我已经设置了一个事件处理程序,当我的应用程序不处于活动状态时,我可以成功地获取热键。这将触发我的应用程序中的一些操作。
我对的行为有问题:
比如说,我按下Cmd-Shift-P键组合。当我释放“P”键时,热键事件就会触发。我需要在所有按键都松开(即:Cmd和Shift键也被释放)时触发或手动触发事件
很容易监控热键,但是我没有看到监控单个按键的内容。如果我能监控修改器键状态,那就可以了。
有什么提示吗?
提前致谢!
更新:
我尝试使用和,但是虽然有效果,但这两个都没有效果,尽管我以完全相同的方式设置了它们。
EventTypeSpec eventTypes[] = {{kEventClassKeyboard, kEventHotKeyReleased}, {kEventClassKeyboard, kEventRawKeyUp}};
// Changing the order in the list does not help, nor does removing kEventHotKeyReleased
OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);
// err == noErr after this line

我无法理解为什么kEventRawKeyUp不会调用globalHotKeyHandler方法,但是会调用kEventHotKeyReleased方法。下面是我的globalHotKeyHandler方法:

OSStatus globalHotkeyHandler(EventHandlerCallRef nextHandler, EventRef anEvent, void *userData) {
    NSLog(@"Something happened!");
}

我是否需要进行其他调用或者我忘记了什么?

N.B: 乍一看,似乎可能是辅助设备访问被禁用了,但实际上并不是。所以我很困惑。


更新2:

我对Leibowitzn建议的CGEventTap进行了一些调查,并得出了以下设置:

CFMachPortRef keyUpEventTap = CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,kCGEventKeyUp,&keyUpCallback,NULL);
CFRunLoopSourceRef keyUpRunLoopSourceRef = CFMachPortCreateRunLoopSource(NULL, keyUpEventTap, 0);
CFRelease(keyUpEventTap);
CFRunLoopAddSource(CFRunLoopGetCurrent(), keyUpRunLoopSourceRef, kCFRunLoopDefaultMode);
CFRelease(keyUpRunLoopSourceRef);

...和回调函数:

CGEventRef keyUpCallback (CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
    NSLog(@"KeyUp event tapped!");
    return event;
}

正如您所看到的,我正在使用kCGEventKeyUp作为事件挂钩的掩码,但不知何故,我收到了鼠标按下事件??!?


更新3:

好了,请忘记那个问题,我忽略了文档中说要使用CGEventMaskBit(kCGEventKeyUp)作为此参数的行,因此正确的调用是:

CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,CGEventMaskBit(kCGEventKeyUp),&keyUpCallback,NULL);

我还有一个问题:修饰键不会触发 kCGEventKeyUp 事件...


更新 4:

好了,再忘掉它吧...今天我提出问题后5分钟必须回答自己的问题!

要拦截修饰键,请使用 kCGEventFlagsChanged 事件:

CGEventTapCreate(kCGHIDEventTap,kCGHeadInsertEventTap,kCGEventTapOptionListenOnly,CGEventMaskBit(kCGEventFlagsChanged),&callbackFunction,NULL);

因此,本质上我已经实现了键和修饰键状态检测,但是我仍然想知道为什么kEventRawKeyUp不起作用...


N.B:还要注意的是,我正在Tiger上开发,目标是尽可能支持新旧版本的操作系统。CGEventTap仅适用于10.4+,因此我现在将使用它,但欢迎提供向后兼容的解决方案。

2个回答

3

我认为有一种方法可以仅监视修改键。请查看Google快速搜索框的源代码。它有一些代码可以检测用户何时双击命令键。请参见: http://www.google.com/codesearch/p?hl=en&sa=N&cd=2&ct=rc#qWBe9JkJhME/trunk/QuickSearchBox/QSB/QSBApplicationDelegate.m&q=modifiersChangedWhileInactive%20package:http://qsb-mac\.googlecode\.com - Leibowitzn
啊,他们只是在注册以下碳事件: { kEventClassKeyboard,kEventRawKeyModifiersChanged } - Leibowitzn
我不确定问题可能是什么。热键是否会干扰?如果禁用热键会发生什么?另外,尝试下载谷歌的快速搜索框。代码非常好。 - Leibowitzn
值得注意的是,OSX 10.9中似乎终于已经弃用了键盘事件的CGEventTap。您肯定会想使用建议的InstallApplicationEventHandler方法。 - ekscrypto
@Leibowitzn,有没有解决方案可以使EventTaps在用户快速输入时正常工作。如果我使用EventTaps,会导致打字变慢。 - Krishna Maru
显示剩余2条评论

1
OSStatus err = InstallApplicationEventHandler(&globalHotkeyHandler, GetEventTypeCount(eventTypes), eventTypes, NULL, NULL);

这不是全局的。只有当您自己的应用程序处于活动状态时,才会安装处理程序,并且(我相信)在Carbon事件管理器自己的事件过滤器之后。

您需要使用InstallEventHandler,它将事件目标作为其第一个参数(InstallApplicationEventHandler是传递应用程序事件目标的宏)。

对于在您的应用程序不活动时发生的事件,您想要的目标是GetEventMonitorTarget()。对于在您的应用程序处于活动状态时发生的事件,您想要的目标是GetEventDispatcherTarget()。要捕获无论哪个应用程序处于活动状态的事件,请在两个目标上安装处理程序。

现在,我建议使用CGEventTaps。


很奇怪,因为InstallApplicationEventHandler在任何地方都安装了我的热键,而不仅仅是在我的应用程序处于活动状态时。我可以从任何应用程序触发热键。此外,当我尝试使用InstallEventHandler和GetEventMonitorTarget()时,似乎它不起作用,但我一定做错了什么。与我现在使用的东西相比,使用CGEventTap有什么优势? - Form
此外,如果你只想要注册一个热键,可以使用RegisterEventHotKey函数,它就是专门为此而存在的。 - Peter Hosey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接