macos OS X:检测系统范围的 keyDown 事件?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4752427/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
OS X: Detect system-wide keyDown events?
提问by Chris Ladd
I'm working on a typing-tutor application for Mac OS X that needs to have keystrokes forwarded to it, even when the application is not in focus.
我正在为 Mac OS X 开发一个打字辅导应用程序,即使该应用程序不在焦点上,它也需要将击键转发给它。
Is there a way to have the system forward keystrokes to the app, possibly through NSDistributedNotificationCenter? I've googled myself silly, and haven't been able to find an answer...
有没有办法让系统将击键转发到应用程序,可能是通过 NSDistributedNotificationCenter?我在谷歌上搜索了自己愚蠢的,并没有找到答案......
EDIT:Sample code below.
编辑:下面的示例代码。
Thanks @NSGod for pointing me in the right direction -- I ended up adding a global events monitorusing the method addGlobalMonitorForEventsMatchingMask:handler:
, which works beautifully. For completeness, my implementation looks like this:
感谢@NSGod 为我指明了正确的方向——我最终使用方法添加了一个全局事件监视器,该方法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);
}];
For me, the tricky part was using blocks, so I'll give a little description in case it helps anyone:
对我来说,棘手的部分是使用块,所以我会给出一些描述,以防它对任何人有帮助:
The thing to notice about the above code is that it's all one single method callon NSEvent. The block is supplied as an argument, directly tothe function. You could think of it kind of like an inline delegate method. Just because this took a while to sink in for me, I'm going to work through it step by step here:
上面代码需要注意的一点是,它都是对 NSEvent 的单一方法调用。该块作为参数直接提供给函数。你可以把它想象成一个内联委托方法。仅仅因为这对我来说需要一段时间才能理解,我将在这里一步一步地解决它:
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
This first bit is no problem. You're calling a class method on NSEvent, and telling it which event you're looking to monitor, in this case NSKeyDownMask. A list of masks for supported event typescan be found here.
这第一位是没有问题的。您正在 NSEvent 上调用类方法,并告诉它您要监视哪个事件,在本例中为 NSKeyDownMask。一个支持的事件类型的面罩的名单,可以发现在这里。
Now, we come to the tricky part: handler, which expects a block:
现在,我们来到棘手的部分:处理程序,它需要一个块:
handler:^(NSEvent *event){
It took me a few compile errors to get this right, but (thank you Apple) they were very constructive error messages. The first thing to notice is the carat ^. That signals the start of the block. After that, within the parentheses,
我花了一些编译错误才做到这一点,但是(感谢 Apple)它们是非常有建设性的错误消息。首先要注意的是克拉 ^。这标志着块的开始。之后,在括号内,
NSEvent *event
Which declares the variable that you'll be using within the block to capture the event. You could call it
它声明了您将在块中用于捕获事件的变量。你可以称之为
NSEvent *someCustomNameForAnEvent
doesn't matter, you'll just be using that name within the block. Then, that's just about all there is to it. Make sure to close your curly brace, and bracket to finish the method call:
没关系,您只需在块中使用该名称即可。然后,这就是它的全部内容。确保关闭大括号和括号以完成方法调用:
}];
And you're done! This really is kind of a 'one-liner'. It doesn't matter where you execute this call within your app -- I do it in the AppDelegate's applicationDidFinishLaunching method. Then, within the block, you can call other methods from within your app.
大功告成!这真的是一种“单线”。您在应用程序中的何处执行此调用无关紧要——我在 AppDelegate 的 applicationDidFinishLaunching 方法中执行此操作。然后,在块内,您可以从您的应用程序中调用其他方法。
采纳答案by NSGod
If you are okay with a minimum requirement of OS X 10.6+, and can suffice with "read-only" access to the stream of events, you can install a global event monitor in Cocoa: Cocoa Event-Handling Guide: Monitoring Events.
如果您对 OS X 10.6+ 的最低要求没问题,并且可以“只读”访问事件流,那么您可以在 Cocoa 中安装全局事件监视器: Cocoa 事件处理指南:监视事件。
If you need to support OS X 10.5 and earlier, and read-only access is okay, and don't mind working with the Carbon Event Manager, you can basically do the Carbon-equivalent using GetEventMonitorTarget()
. (You will be hard-pressed to find any (official) documentation on that method though). That API was first available in OS X 10.3, I believe.
如果您需要支持 OS X 10.5 及更早版本,并且只读访问是可以的,并且不介意使用 Carbon Event Manager,您基本上可以使用 GetEventMonitorTarget()
. (不过,您将很难找到有关该方法的任何(官方)文档)。我相信,该 API 最早出现在 OS X 10.3 中。
If you need read-write access to the event stream, then you will need to look at a slightly lower-level API that is part of ApplicationServices > CoreGraphics:CGEventTapCreate()
and friends. This was first available in 10.4.
如果您需要对事件流进行读写访问,那么您需要查看一个稍微低级的 API,它是 ApplicationServices > CoreGraphics:CGEventTapCreate()
和朋友的一部分。这在 10.4 中首次可用。
Note that all 3 methods will require that the user have "Enable access for assistive devices" enabled in the System Preferences > Universal Access preference pane (at least for key events).
请注意,所有 3 种方法都要求用户在“系统偏好设置”>“通用访问”偏好设置面板中启用“启用辅助设备访问”(至少对于关键事件)。
回答by micho
I'm posting the code that worked for my case.
我正在发布适用于我的案例的代码。
I'm adding the global event handler after the app launches. My shortcut makes ctrl+alt+cmd+T open my app.
我在应用程序启动后添加全局事件处理程序。我的快捷方式让 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];
}
}];
}
回答by Sitses
As NSGod already pointed out you can also use CoreGraphics.
正如 NSGod 已经指出的,你也可以使用 CoreGraphics。
In your class (e.g. in -init):
在您的课程中(例如在 -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();
Outside of the class, but in the same .m file:
在课堂之外,但在同一个 .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;
}
回答by Duane
The issue I find with this is that any key registered globally by another app will not be cought... or at least in my case, perhaps I am doing something wrong.
我发现的问题是,另一个应用程序全局注册的任何密钥都不会被咳嗽……或者至少在我的情况下,也许我做错了什么。
If your program needs to display all keys, like "Command-Shift-3" for example, then it will not see that go by to display it... since it is taken up by the OS.
如果您的程序需要显示所有键,例如“Command-Shift-3”,那么它不会看到显示它......因为它被操作系统占用。
Or did someone figure that out? I'd love to know...
或者有人想出来了吗?我很想知道...