在某些应用中,应用直接处理键盘快捷键是有意义的,否则这些快捷键绑定到系统范围的组合。例如,⌘-空格键(通常是聚光灯)或 ⌘-Tab(通常是应用切换器)。这适用于各种Mac应用程序,例如VMWare Fusion,Apple自己的屏幕共享和远程桌面客户端(分别将事件转发到VM或服务器,而不是在本地处理它们),以及App Store中的一些类似的第三方应用程序。
我们想在我们正在开发的应用程序中实现这样的模式,但很难弄清楚如何做到这一点。我应该指出,有问题的应用程序是一个常规的前台应用程序,是沙盒的,任何解决方案都必须符合App Store规则。商店中的其他应用程序可以做到这一点的事实意味着这必须是可能的。
需要明确的是,我们希望:
- 检测并处理所有按键,包括绑定到全局快捷键的按键。
- 防止全局快捷方式触发其全局绑定效果。
Apple 的事件体系结构文档建议前台应用程序应该已经接收这些事件。(它只谈论早期关卡处理诸如电源和弹出按钮之类的东西,这很好。它继续建议,关键事件文档还暗示NSApplication
的sendEvent:
方法是根据修饰符标志检测潜在的快捷方式,将它们调度到窗口,如果失败,则发送到菜单栏。它没有明确说明全局绑定的快捷方式会发生什么。
我尝试子类化NSApplication
并覆盖sendEvent:
.无论我是否将所有事件传递给超类实现,或者如果我说,过滤器修饰键事件,当我按 ⌘-Space 时,我会收到按下和释放命令 (⌘) 键的事件,而不是空格键。聚光灯 UI 始终弹出。
我还没有从Apple或其他公司找到太多关于NSApplication及其早期事件处理的信息。我似乎无法找出在什么级别检测和处理全局快捷方式。
有人可以指出我正确的方向吗?
可能
的解决方案不起作用:我在其他Stack Overflow帖子中看到的建议,但不适用于我看到的其他应用程序,这些应用程序这样做(并且会违反App Store规则):
- 辅助功能接口(需要特殊权限)
- 事件点击/钩子(需要以 root 身份运行)
无论如何,这两种方法都是矫枉过正的,因为它们可以让您随时拦截所有事件,而不仅仅是在您的应用程序是前台应用程序时。
同时,NSevent
的addGlobalMonitorForEventsMatchingMask:handler:
不会阻止全局快捷方式处理程序为这些事件触发,所以我什至懒得尝试。
好的,所以Cocoa事件方法和Quartz事件点击已经出来,因为它们要么需要root或可访问性访问,要么在停靠之前不捕获事件。
Carbon的PushSymbolicHotKeyMode
已经出来了,因为根据文档,它需要可访问性访问。
Carbon的RegisterEventHotKey
可能已经出来了,因为苹果似乎不允许它(请参阅我对这个问题的评论中的链接)。但是,即便如此,我测试过,您也不能使用它来捕获 Command+Tab。
我快速验证了它是如何工作的,但是 YMMV:
- 实现此答案中的
KeyboardWatcher
示例类。您将需要链接IOKit。 - 添加硬件 - USB (com.apple.security.device.usb) 沙盒授权。这是必需的,因为键盘观察程序使用 HID 来捕获按键
Handle_DeviceEventCallback
将为您提供按下的键。您显然可以根据需要对其进行修改- 使用
SetSystemUIMode
阻止任务切换器和聚光灯。您将需要链接碳。
SetSystemUIMode(kUIModeContentSuppressed, kUIOptionDisableProcessSwitch);
请注意,这仅在应用处于前台时有效(可能是您想要的)。我使用跟踪矩形在我的视图上设置了它,因此它仅在鼠标悬停在我的视图上时才生效(如在 Remotix 中):
- (void)viewDidLoad {
[super viewDidLoad];
NSTrackingArea* trackingArea = [[NSTrackingArea alloc] initWithRect:[self.view bounds] options: (NSTrackingMouseEnteredAndExited |
NSTrackingActiveAlways) owner:self userInfo:nil];
[self.view addTrackingArea:trackingArea];
}
- (void) mouseEntered:(NSEvent*)theEvent {
SetSystemUIMode(kUIModeContentSuppressed, kUIOptionDisableProcessSwitch);
}
- (void) mouseExited:(NSEvent*)theEvent {
SetSystemUIMode(kUIModeNormal, 0);
}
Remotix似乎链接了Carbon和IOKit,但我看不到它们是否具有USB权利(我尝试了演示,而不是App Store版本)。他们可能正在做这样的事情。
实现此目的的正常方法是安装石英事件水龙头。但是,要接收针对其他应用程序的事件,您需要(如您所说)是根用户,或者为您的应用程序启用辅助功能访问权限。
似乎无法在当前的沙盒规则中使用事件点击。这在开发人员论坛中得到了确认。该链接仅是登录,但引用线程:
是否有机会通过阻止启动iTunes来处理来自媒体键的事件。在沙盒之前,可以通过创建CGEventTap来实现,但现在沙盒拒绝使用hid-controll。
不可以,这目前在应用沙盒中是不可能的。
我不确定还有另一种方法可以做到这一点;我很想知道App Store中的哪些应用程序可以?
VMWare Fusion显然没有沙盒化,苹果自己的应用程序不受规则的约束。请记住,沙盒仅在 2012 年推出后添加的新应用上强制执行。在该日期之前添加的应用不会强制执行沙盒。看到这个答案。
我很久以前就解决了这个问题,但我只是注意到我从来没有在这里发布过它。答案最终涉及CGSSetGlobalHotKeyOperatingMode()
.这不是一个公共API,但有许多Mac App Store应用程序通过混淆函数名称并动态查找来使用它。苹果似乎并不介意。该 API 使用起来非常简单,并且有很多开放的示例源代码。
对于寻找全屏应用解决方案的其他人,或者如果您愿意接管全屏,您可以使用:CGDisplayCapture
这将导致您的应用捕获所有键盘输入,甚至阻止使用键盘调用 Spotlight 和应用切换。
import Quartz
// disable keyboard events for all apps, except yours
CGDisplayCapture(CGMainDisplayID())
// reenable keyboard events for other apps
CGDisplayRelease(CGMainDisplayID())
注意:在发布显示之前,应用不会接收窗口/应用程序活动/辞职事件。因此,也许您可以使用鼠标跟踪在应用处于活动状态时释放显示。此外,即使是屏幕保护程序/锁定屏幕也会受到影响。确保根据需要停用捕获。
裁判:
- https://lists.apple.com/archives/cocoa-dev/2012/Sep/msg00476.html
- 监视屏幕保护程序事件
- 显示捕获