macOS App:处理绑定到全局键盘快捷键的键组合

时间:2017-06-10 11:39:37

标签: macos cocoa keyboard-shortcuts keyboard-events nsevent

在某些应用程序中,应用程序直接处理键盘快捷键是有意义的,否则键盘快捷键将绑定到系统范围的组合。例如,⌘-Space(通常是Spotlight)或⌘-Tab(通常是app切换器)。这适用于各种Mac应用程序,例如VMWare Fusion,Apple自己的屏幕共享和远程桌面客户端(分别将事件转发到VM或服务器,而不是在本地处理它们),以及应用程序中的一些类似的第三方应用程序存储。

我们希望在我们正在开发的应用程序中实现这样的模式,但是很难确定如何做到这一点。我应该指出,有问题的应用程序是一个常规的前台应用程序,是沙箱,任何解决方案都必须符合App Store规则。事实上,商店中的其他应用程序可以做到这一点意味着必须这样做。

要明确,我们希望:

  • 检测并处理所有按键操作,包括绑定到全局快捷键的按键。
  • 防止全局快捷方式触发其全局绑定效果。

Apple的Event Architecture document表明前台应用程序应该已经接收了这些事件。 (它只涉及处理电源和弹出按钮等事情的早期级别,这很好。)它继续建议,而NSApplication sendEvent: NSApplication方法是key events document also implies什么检测基于修饰符标记的潜在快捷方式,将它们分派到窗口,如果失败,则转到菜单栏。它没有明确说明全局绑定快捷方式会发生什么。

我尝试了对sendEvent:进行子类化并覆盖NSevent。无论我是否将所有事件都传递给超类实现,或者如果我说过滤器修改键事件,当我按⌘-Space时,我会收到按下并释放命令(⌘)键的事件,但不会显示空格键。 Spotlight UI总是会弹出。

我没有从Apple或其他方面找到有关子类化NSApplication及其早期事件处理的更多信息。我似乎无法找出检测和处理全局快捷方式的级别。

有人可以指出我正确的方向吗?

无效的可能解决方案:

建议我在其他Stack Overflow帖子中看到但不适用于我见过的其他应用程序(这会破坏App Store规则):

  • Accessibilty API(需要特别许可)
  • 事件点击/挂钩(需要以root身份运行)

无论如何,这些都是过度的,因为它们允许您在所有时间拦截所有事件,而不仅仅是当您的应用是前台应用时。

addGlobalMonitorForEventsMatchingMask:handler:的{​​{1}}同时不会阻止全局快捷方式处理程序为这些事件触发,所以我甚至都不打算尝试它。

3 个答案:

答案 0 :(得分:1)

好的,所以Cocoa事件方法和Quartz事件抽头都没有了,因为它们需要root或accessibility访问权限,或者在dock之前没有捕获事件。

Carbon PushSymbolicHotKeyMode已经出局,因为根据文档,它需要访问权限。

Carbon RegisterEventHotKey可能已经出局,因为Apple似乎不允许这样做(请参阅我在评论中的链接)。但是,即便如此,我测试过,你无法使用它来捕获Command + Tab。

我快速证明了这个 的工作原理,但YMMV:

  • 实现此answer中的KeyboardWatcher示例类。您需要链接IOKit。
  • 添加硬件 - USB(com.apple.security.device.usb)沙盒权利。这是必要的,因为KeyboardWatcher使用HID来捕获按键
  • Handle_DeviceEventCallback将为您提供按下的键。显然,您可以根据需要对其进行修改
  • 使用SetSystemUIMode阻止任务切换器和Spotlight。你需要链接Carbon。

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版本)。他们有可能做这样的事情。

实现这一目标的正常方法是安装Quartz事件。但是,要接收定位到其他应用程序的事件,您需要(如您所说)为root用户,或者为您的应用启用辅助功能访问权限。

似乎无法使用当前沙盒规则的事件点击。这已在developer forum中得到确认。该链接仅限登录,但引用该主题:

  

是否有机会通过阻止启动iTunes来处理来自媒体键的事件。在沙盒之前,可以通过创建CGEventTap但现在使用hid-controll进行沙箱拒绝。

     

不,目前在App Sandbox中无法实现。

我不确定另一种方法可以做到这一点;我有兴趣知道App Store中的哪些应用可以?

VMWare Fusion显然不是沙盒,Apple自己的应用程序不受规则约束。请记住,沙盒仅在2012年引入后添加的新应用程序上强制执行。在该日期之前添加的应用程序没有强制执行沙盒。请参阅此answer

答案 1 :(得分:1)

对于正在为全屏应用程序寻找解决方案的其他人,或者如果您愿意接管全屏应用程序,则可以使用:CGDisplayCapture

这将导致您的应用捕获所有键盘输入,甚至无法使用键盘调用Spotlight和应用切换。

import Quartz

// disable keyboard events for all apps, except yours
CGDisplayCapture(CGMainDisplayID())

// reenable keyboard events for other apps
CGDisplayRelease(CGMainDisplayID())

注意:在释放显示器之前,该应用程序将不会收到窗口/应用程序活动/退出事件。因此,也许可以在应用程序处于活动状态时使用鼠标跟踪来释放显示。此外,即使屏保/锁定屏幕也将受到影响。确保根据需要停用捕获。

参考:

答案 2 :(得分:0)

我很久以前解决了这个问题,但是我只是注意到我从未在这里发表过。答案最终涉及CGSSetGlobalHotKeyOperatingMode()。这不是公共API,但是许多Mac App Store应用程序通过混淆函数名称并动态查找来使用它。苹果似乎并不介意。该API非常易于使用,并且有很多开放的示例源代码在浮动。