macOS 鍵盤按鍵 event 的三種截獲方式

一般情況下,你不需要了解這些內容。

在極少數情況下,你的app可能需要去獲取用戶按下的按鍵信息,比如盜號木馬 開發一款輸入法。只有這樣你才能給用戶提供候選。

怎麼在 macOS 下創建一個輸入法,我在Swift 使用 InputMethodKit 寫輸入法這篇文章中有詳細的說明,這里略過不提,我們重點放在如何處理用戶按鍵,尤其是修飾按鍵的處理上。

落格輸入法一直以來有一個不大不小的 bug,就是在某些輸入框裡,按 shift 按鍵是無法正確切換中英文的。說點專業的,就是說輸入法在某些輸入框下無法獲取這個 shift 的按鍵 event。

注意,輸入是正常的,其他一切都是正常的。就是對於輸入法來說,shift按鍵從來沒按下過。

默認情況下,我們是繼承 IMKInputController ,通過其中的 打開 覆蓋 FUNC 處理(_ 事件: NSEvent!, 客戶 寄件人: Any!) -> 布爾 來處理按鍵信息的,系統會自動調用這個函數給我們發來數據,通過返回的 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 的事件

然後是供回調的閉包函數:

值得注意的是由於這裡調用了 C 函數,所以這個閉包不能捕獲上下文(這裡我就寫成函數了,因為內容比較多),同時,也是同樣的原因,這個函數必須在 Swift 下是全局函數,即寫在所有的大括號外邊即可。

這裡還要注意高亮的一行,我們用通知中心把消息傳遞給輸入法,由於輸入法類的實例生存週期不確定(一般來說實際上是一個輸入框就有一個實例,每次輸入框獲得焦點都會重新生成一個實例但舊實例不會立即釋放),我們無法直接調用輸入法的實例來實現參數的傳遞。

IMKInputController 類中,有一對監控輸入法進行輸入還是結束輸入的回調,我們利用這個回調來實現監聽的接收和釋放,避免多個輸入法實例同時監聽導致多線程資源競用:

這樣一來,每次進入輸入模式註冊監聽,離開輸入模式則註銷監聽。至於哪個實例來監聽,這是由系統保證的,除了我們的修飾符外,其他的按鈕也是依舊由系統保證什麼時侯發送給哪個實例,一切都完美了。

 

最後,補上通知監聽回調的處理:

這裡有一個 GitHub 倉庫,裡邊是用 Objective-C 實現的鍵盤按鍵監聽,供你參考:/鍵盤記錄

由於IOKit

這個框架可能對一些驅動開發和串口開發的開發者來說會很熟悉,這是蘋果最低層的框架,你可以不用觸及內核而直接與接入 macOS 的外設硬件進行交互,對於我們來說,這一層太過深奧,用 Swift 調用這一層的內容也不是很舒服。

總之,如果你想了解更多,這裡有一個 GitHub 倉庫,/斯威夫特 - 鍵盤記錄這個項目使用 Swift 4 構建,直接使用最低層的 IOKit 與 HID 設備溝通,供你參考。

 

參考文獻

如何掛斷 / 重新映射在OSX任意鍵盤事件?

由...出版 R0uter

如非聲明,本人所著文章均為原創手打,轉載請註明本頁面鏈接和我的名字。

加入對話

2 註釋

您的電子郵件地址不會被公開. 必填字段標 *