macOS 的 Accessibility
這兩天算是做了一件長久以來沒做的事。
macOS 的 Accessibility

小麥輸入法的選字窗支援 macOS 的 accessibility
這兩天算是做了一件長久以來沒做的事。
以下的內容都以 macOS 15 為主。
自從開始弄 macOS 上的香草/小麥輸入法以來,一直沒有弄 accessibility 這塊 — 也就是,當用戶打開了 VoiceOver 打字,而當輸入法的選字窗出現的時候,應該要讓 VoiceOver 念現在選字窗當中到底選到了哪個候選字。
macOS 內建的輸入法在這塊做得很好,但是在香草/小麥上面一直沒做,主要原因還是在於,這些輸入法主要是為了滿足自己的需要,一直以來也沒聽到有人在使用這些輸入法的時候會打開 VoiceOver,以一個要付房貸的中年人來說,很難把這件事排入生活中的優先順序。不過七月的時候有人回報,說 VoiceOver 打開的時候,小麥輸入法的選字窗會始終出不來。這問題聽起來就很奇怪,這一陣子就找了時間看一下。
同樣是蘋果的平台,跟 iOS 相比,macOS 上的 accessibility 難搞許多。iOS 的畫面組成相對靜態,整個應用程式大多是以一個 window 組成,所有的功能都在這個 window 當中,只要每個 UI 元件當中設定好了 accessibility label,大概都可以讓 VoiceOver 讀到 — 當然當中也有一些讓人覺得很奇妙的設計,像是在某些裡頭塞了圖片的元件上,你往這些元件直接塞 accessibility label 還沒用,反而是要塞在 image 物件上。
但 macOS 上的應用程式則是由多個 window 組成的,window 還會有是不是 main window、key window 的區別,也就是說,VoiceOver 所該在的焦點,不一定會在目前用戶所在的 window 上。輸入法的選字窗就是一個明顯的例子,用戶的鍵盤輸入焦點一定是在原本使用的軟體中(文書軟體啦、瀏覽器啦…),但是跳出選字窗的時候,VoiceOver 該念的是另外一個視窗。另一個麻煩的,則是這邊念完之後,還要把焦點交還給 main window。
先講那個 VoiceOver 打開之後,會讓選字窗始終出不來的問題。我們在選字窗當中使用了 table view,在 macOS 15 上,當預設的 table view (NSTableView)出現的時候,不但會直接變成 VoiceOver 的焦點,還會順便變成鍵盤輸入的焦點。而輸入法的實作邏輯是,如果原本輸入的焦點跑掉,就會自動關閉選字窗,結果就變成 — 因為輸入焦點跑到選字窗上,結果選字窗反而被關掉,而且輸入焦點還離開了原本的應用程式。macOS 14 之前反而沒這個問題。
所以第一步很簡單,先 subclass NSTableView,把 isAccessibilityElement 設成 false,不要讓 table view 搶走輸入焦點。而既然開始弄 accessibility 了,那就順道把 accessibility 該有的功能做完。就像最近的流行語 — 來都來了。
在 macOS 上,要適應多 window 的環境,就需要呼叫 NSAccessibility 的 post(element: Any, notification: NSAccessibility.Notification) ,利用通知的方式,主動告知 VoiceOver 目前視窗環境的變化。輸入法會用到的通知包括:
當選字窗出現時,呼叫
NSAccessibility.post(element: window, notification: .created)
NSAccessibility.post(element: window, notification: .focusedWindowChanged)
代表我們讓一個新視窗出現了,而且應該變成焦點。這時候 VoiceOver 會把整個選字窗的內容念一遍。這個通知只需要在新視窗出現的時候呼叫一次,不然每次呼叫,VoiceOver 都會整個念一次。
接著,我們要呼叫一次
NSAccessibility.post(element: candidateElement,
notification: .focusedUIElementChanged)
NSAccessibility.post(element: candidateView,
notification: .selectedChildrenChanged)
我們的選字窗中,所有的候選字都放在 candidateView 當中,而 candidateView 的每個候選字,這邊則用 candidateElement 代表。這段 code 的意思是,整個 candidateView 當中被選擇的選項改變了,而用 NSAccessibility.Notification.focusedUIElementChanged ,才會讓 VoiceOver 去念我們希望去念的選項。
到這邊雖然可說實作了 accessibility 功能,但在實際使用上,卻幾乎是沒有意義的。在注音輸入法的選字窗當中,每個字的念法是一樣的,像是打了一個「中」,選字窗出現的是「終」、「鐘」、「忠」…。眼睛看不到的使用者無法分辨出到底是哪個字,所以要能提供更多的線索,像是「終於的終」、「忠誠的忠」等等。
小麥注音本身有一套聯想詞詞庫,這時候可以派上用場,但聯想詞當中的詞庫相當有限。 — 那麼,macOS 內建的輸入法,是怎麼做到 accessibility 功能的呢?蘋果也沒有提供官方 API。
花了一點時間,找到了一個有趣的 Sqlite 檔案
/System/Library/PrivateFrameworks/CoreChineseEngine.framework/Versions/A/Resources/CharacterAccessibilityData.sqlite
來看看裡頭有什麼?

來 select 看看

利用這個檔案,如果輸入麥,就可以得到「小麥」這個範例詞彙,或是知道「麥」是用「來夂」兩個部分組成的。對於沒有範例的字來說,就可以用「這是用什麼字組成的字」提供說明。像是「龘」這個字—

就是「龍龍龍」組成的。
macOS 系統裡頭,還有兩個有意思的 sqlite 資料庫。在 /System/Library/Input Methods/CharacterPalette.app/Contents/Resources 目錄下,有 CharacterDB.sqlite3 與 RelatedCharDB.sqlite3 兩個檔案。
在 CharacterDB.sqlite3 裡頭,可以找到某個字在中日韓等語言中的讀音,包括漢語拼音、注音、倉頡碼等。

查 emoji 也有英文的相關解釋。

至於 RelatedCharDB.sqlite3 則提供各種異體字的資訊。像是去查「麥」這個字:

就可以知道有「⿆麥麦」等不同寫法。
메타데이터
- post_id
- 823ccc2a3743
- slug
- macos-的-accessibility-823ccc2a3743
- url
- https://medium.com/@zonble/macos-%E7%9A%84-accessibility-823ccc2a3743
- canonical_url
- https://medium.com/@zonble/macos-%E7%9A%84-accessibility-823ccc2a3743
- author_url
- https://medium.com/@zonble
- status
- ok
- fetched_at
- 2026-09-01 08:15:51