Categories
Mac OS Swift 落格输入法

macOS 键盘按键 event 的三种截获方式

一般情况下,你不需要了解这些内容。

在极少数情况下,你的app可能需要去获取用户按下的按键信息,比如盗号木马 开发一款输入法。只有这样你才能给用户提供候选。

怎么在 macOS 下创建一个输入法,我在Swift 使用 InputMethodKit 写输入法这篇文章中有详细的说明,这里略过不提,我们重点放在如何处理用户按键,尤其是修饰按键的处理上。

落格输入法一直以来有一个不大不小的 bug,就是在某些输入框里,按 shift 按键是无法正确切换中英文的。说点专业的,就是说输入法在某些输入框下无法获取这个 shift 的按键 event。

注意,输入是正常的,其他一切都是正常的。就是对于输入法来说,shift按键从来没按下过。

默认情况下,我们是继承   IMKInputController ,通过其中的 open override func handle(_ event: NSEvent!, client sender: Any!) -> Bool 来处理按键信息的,系统会自动调用这个函数给我们发来数据,通过返回的 Bool 来判断这个 event 是否要继续传递,一般的处理是没问题的,但对于修饰符,则会出现上文中的 bug。

当然,这个原因我想大家都能想的出来,就是我们获取按键信息的层级太高了,经过了层层传递才到我们的手中,这时候这个 event 已经被改了太多次,天知道它在哪里被拦截了,所以我们要想办法从更底层的地方去获取这个 event。

Event 传递的三个层级

在 macOS ,Event 传递经过了三个层级:

  • Cocoa/AppKit ——这是最高层级,也是我们默认在用的级别 NSEvent ;
  • Quartz 会把来自 IOKit 的 event 发送到各个 app,也就是 CGEvent ;
  • IOKit — 最底层,直接来自硬件。
插图来自 https://www.codesd.com/item/how-to-hang-up-remap-an-arbitrary-keyboard-event-on-osx.html

NSEvent

这个我们最常见,它里边封装了按键的代码、修饰符标识等等——最大的特点是 shift 不分左右(其他的也不分哈)。

一般来说,我们用它是足够的,比如判断一些组合键,判断用户输入了什么内容。但是对于上文的 bug,尤其是在一些游戏里,输入无法切换中英文,所以,我们需要寻求更底层的解决办法。

CGEvent

NSEvent  来自 CGEvent  的封装,后者更底层一些,在这一层你就可以得到区分左右的 shift 编码了,当然,这样一来  NSEvent.ModifierFlags 中的枚举类型就直接都不能用了,因为这里的编码不区分左右。这下你就需要自己去根据按键编码判断组合键了。

不过,对于我们的输入法来说,考虑到结构问题,我们并不在这一层获取所有的按键信息,只要获取组合键即可,这样一来,默认的数据不变,只要改变组合键的判断就行了,如此得到的另外一个收益就是可以区别对待每一个修饰按键了,都能区分左右——如果你确实需要的话。

首先,是在系统里添加一个监听,这个要写在你的  AppDelegate 里,确保一旦程序启动就执行:

这里我们设定了只监听  flagsChanged 的 event

然后是供回调的闭包函数:

值得注意的是由于这里调用了 C 函数,所以这个闭包不能捕获上下文(这里我就写成函数了,因为内容比较多),同时,也是同样的原因,这个函数必须在 Swift 下是全局函数,即写在所有的大括号外边即可。

这里还要注意高亮的一行,我们用通知中心把消息传递给输入法,由于输入法类的实例生存周期不确定(一般来说实际上是一个输入框就有一个实例,每次输入框获得焦点都会重新生成一个实例但旧实例不会立即释放),我们无法直接调用输入法的实例来实现参数的传递。

IMKInputController 类中,有一对监控输入法进行输入还是结束输入的回调,我们利用这个回调来实现监听的接收和释放,避免多个输入法实例同时监听导致多线程资源竞用:

这样一来,每次进入输入模式注册监听,离开输入模式则注销监听。至于哪个实例来监听,这是由系统保证的,除了我们的修饰符外,其他的按钮也是依旧由系统保证什么时侯发送给哪个实例,一切都完美了。

 

最后,补上通知监听回调的处理:

这里有一个 GitHub 仓库,里边是用 Objective-C 实现的键盘按键监听,供你参考:/keylogger

IOKit

这个框架可能对一些驱动开发和串口开发的开发者来说会很熟悉,这是苹果最低层的框架,你可以不用触及内核而直接与接入 macOS 的外设硬件进行交互,对于我们来说,这一层太过深奥,用 Swift 调用这一层的内容也不是很舒服。

总之,如果你想了解更多,这里有一个 GitHub 仓库,/Swift-Keylogger这个项目使用 Swift 4 构建,直接使用最低层的 IOKit 与 HID 设备沟通,供你参考。

 

参考文献

How to hang up / remap an arbitrary keyboard event on OSX?

本文由 落格博客 原创撰写:落格博客 » macOS 键盘按键 event 的三种截获方式

转载请保留出处和原文链接:https://www.logcg.com/archives/2902.html

By 落格博客

如非声明,本人所著文章均为原创手打,转载请注明本页面链接和我的名字。

2 replies on “macOS 键盘按键 event 的三种截获方式”

发表评论

电子邮件地址不会被公开。 必填项已用*标注