怎樣在 Mac OS X 下寫一套輸入法?(二)

講完了資源檔案,接下來,我們要看的是程式的部份。我們首先要看CIMcomponent.cpp這個檔案,這個地方的程式碼,是讓你所要寫的輸入法,足成為一個系統元件的溝通介面,用來接收各種來自作業系統的事件,並且給予回應,用蘋果的官方術語來說,就是各種低階例行事件(Low-levelRoutines)。

一般來說,在進行輸入法開發的時候,幾乎都不會修改這個部份的程式碼,但是要進一步了解輸入法的運作,就必須要解說,一個輸入法系統元件會處理那些事件,才能夠知道我們要在什麼時候,讓輸入法做什麼事情。您也可以先參看蘋果的這幾篇官方文件:Text Services Manager Reference: Low-levelRoutineSelectors,以及Component Manager Reference: Request Codes

在CIMComponentDispatch這個地方,可以看到使用case、switch語法處理各種事件,這些事件包括:

  • kComponentOpenSelect (以下是蘋果的元件管理員Component Manager呼叫的事件)
  • kComponentCloseSelect
  • kComponentCanDoSelect
  • kComponentVersionSelect
  • kCMGetScriptLangSupport(以下是文字服務—TSM,Text Service Manager呼叫的事件)
  • kCMInitiateTextService
  • kCMTerminateTextService
  • kCMActivateTextService
  • kCMDeactivateTextService
  • kCMTextServiceEvent
  • kCMGetTextServiceMenu
  • kCMFixTextService
  • kCMHidePaletteWindows

在元件管理員部份的事件,主要處理系統要載入或關閉這個系統元件這類的事件,絕大部份都是直接 return noErr。而其中的 CanDoSelect,則是要告訴系統,這個系統元件可以做那些事情,因為輸入法是一個文字服務的元件,所以輸入法會在 CanDoSelect 這個地方,告訴系統,它可以處理以下各種文字服務事件。

接著來看文字服務的例行事件,可以注意到,Carbon Input Method 中並沒有使用前述Text Services Manager Reference: Low-levelRoutineSelectors這篇文件所表列的所有TSM事件,沒有列出的原因,是因為這些事件在 Mac OS X 裡頭,作業系統都已經幫我們先做好了。在 Carbon Input Method 中,需要特別注意的事件,都寫成獨立的函式給予回應,這些函式都放在 CIM.cpp 裡頭,也就是,如果你使用 CIM 開始改寫您自己的輸入法,那麼 CIM.cpp 才是您特別需要花力氣的地方。

當您在某個應用程式中切換到了您的輸入法,或是您在已經選用了您的輸入法之後,開啟了一個新的應用程式,首先會出現 InitiateTextService 事件,代表開啟了一個新的輸入法的 session,並進行各種初始化工作,請注意,Mac OS X的設計是在每個不同的應用程式中,分別跑一次 session,換言之,您開了多少應用程式,就等於跑了多少次的輸入法,而如果你要輸入法從某個檔案中讀取資料,便是在這個地方進行。

而這也就是在 SpaceChewing 以及 OpenVanilla 裡頭的酷音輸入法出現設計缺陷的原因。酷音輸入法提供了手動增加新詞的功能,但是因為每開一次應用程式,就載入一次酷音詞庫,所以每個應用程式中,都載入了一分酷音詞庫,而如果在其中一個應用程式中,在酷音輸入法裡頭加入了新詞,其他還在使用中的應用程式中的酷音詞庫,並不會隨之同步更新,更有甚者,酷音的手動詞庫採用將所有資料一次載入、然後一次將記憶體中的資料寫入某個檔案的方式,所以,已經有了新詞的詞庫檔案,很有可能被另外一份沒有新詞的詞庫資料所覆蓋,造成加詞功能因此失效。這是目前亟需要改進的問題。

而在關閉應用程式,或切換到另外一個輸入法的時候,就會發生 kCMTerminateTextService 事件,終結這個 session。

在初始化之後,就會產生 kCMActivateTextService 事件,代表這個 session 正在使用中;前面提到,在不同的應用程式中,都會個別有一個輸入法的 session,而正在使用中的 session 與在背景沒有使用的 session,行為是不一樣的。也就是,當你切換到某個應用程式的時候,這個 session 就會從未使用中,變為使用中,在這個視窗中就發生了 kCMActivateTextService 事件,而從使用中變成未始用中的視窗,就發生了 kCMDeactivateTextService 事件。

所以在 kCMActivateTextService 與 kCMDeactivateTextService 事件中要注意的,就是如何處理打字打到一半,切換應用程式的狀況。例如,在原本的應用程式視窗中,叫出了候選字列表視窗(以下簡稱選字窗),在切換到另外一個應用程式視窗的時候,就必須把選字窗隱藏起來,重新切回這個應用程式視窗的時候,就必須要把已經隱藏起來的選字窗叫回來…諸如此類的事情。

kCMTextServiceEvent 就是實際處理各種鍵盤事件(打了什麼字進去,用了那些如 Ctrl 、 Command 等組合按鈕等)的部份,kCMGetTextServiceMenu 所給予的回應,是輸入法選單當中的內容,容後再述。

在 kCMFixTextService 的部份比較麻煩,官方文件非常語焉不詳,說,這是要求使用者確認是否要送出文字,而並沒有明確指出什麼狀況下會發生這個事件,而在開發過程中才發現是這樣的:在任何一個應用程式裡頭打字時,都是滑鼠游標點在某個地方,然後開始打字,然後文字游標會隨著打出的字一起往前移動,但如果在打字緩衝區(也就是像使用各種內建輸入法打字的時候,文字底下還出現一條底線的狀態,代表文字還處在一種暫存的狀態,還沒有確實送到應用程式中)有東西的時候,用滑鼠點選應用程式視窗的其他地方,會發生什麼事情?這時候系統會直接把緩衝區的內容送到應用程式,游標也移動到了其他位置,並發生 kCMFixTextService 事件。

所以對於 kCMFixTextService 事件該做的回應就是,如果你的輸入法程式用了某個內部資料,暫存輸入的鍵盤事件,例如,你可能寫了一個無蝦米輸入法,打了oao,然後你用了一個字串將oao儲存起來,並且在緩衝區顯示了oao,在 kCMFixTextService 事件發生的時候,「oao」便已經送到了應用程式中,但是當你繼續打字,就會發現因為oao沒有清空,結果緩衝區裡頭,又跑出了「oao」。

最後是kCMHidePaletteWindows。一套輸入法程式可能還包含各種小面板,例如螢幕鍵盤、或是一個快速切換各種功能的提示面板,送出kCMHidePaletteWindows的時候,代表把這些面板隱藏起來。因為 CIM 到 OpenVanilla 的開發中,開發團隊都覺得會把程式弄得過份複雜,所以沒有做這方面的設計,所以,如果要看 kCMHidePaletteWindows 的範例,請參見 BIM 的寫法。

One thought on “怎樣在 Mac OS X 下寫一套輸入法?(二)

  1. Pingback: cat jjgod >> log » Blog Archive »

Comments are closed.