LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 2153|回复: 2

[转载]怎样在MacOSX下写一套输入法!

[复制链接]
发表于 2007-1-30 11:36:46 | 显示全部楼层 |阅读模式
使用macosx系统很久了,总是对其中文输入法不太满意。本人水平有限,没有搞过开发。最近查到些关于macosx下输入法的文章,希望可以起到抛砖引玉的作用!

这篇文章的作者是zonble,OpenVanilla的作者之一。Glider在开发QIM的时候,也曾咨询过OV的开发者。
文章出处:
http://zonble.twbbs.org/archives/2005_07/789.php
http://zonble.twbbs.org/archives/2005_07/790.php
http://zonble.twbbs.org/archives/2005_07/791.php
我使用网通的adsl无法访问该地址,现在把文章内容帖过来:
Glider 最近推出了 [color="Red"]QIM的体验版本。QIM 是一套在 Mac OS X 環境下的簡體中文拼音輸入法,而在 glider 開始開發 QIM 的時候,曾經有向 OpenVanilla 的開發團隊詢問有沒有關於在 Mac OS X 下開發輸入法的相關文件,而那時候居然答不出來,一方面是因為蘋果官方本身所提供的文件就不多,甚至有許多與作業系統溝通的方式,都還找不到文件,在開發 OpenVanilla 的時候,也都是在缺乏文件的狀況下摸索,而在摸索的同時,自己卻也沒有留下多少文件。想想這樣還是不行,還是該來弄一點文件。
要在 Mac OS X 下開發一套輸入法,可以有許多選擇,一種就是開發成 OpenVanilla 的模組: OpenVanilla 計畫的目標,就在於簡化開發輸入法的難度,例如各種輸入法大概都會用到的候選字視窗、文字緩衝區(buffer)、提示訊息的行為,資料的編碼格式轉換等等, lukhnos 都已經包裝成了物件,所以只要決定這些物件在什麼時候該做什麼事情,就可以弄一套輸入法模組出來,而在 OpenVanilla 中,已經有一套「通用輸入法模組」,只要準備好一個純文字檔案,寫好按鍵與字碼之間的對應,就可以成為一套輸入法。
而如果說 OpenVanilla 是一套輸入法,其實是很含糊的說法,因為 OpenVanilla 包含非常多的部份,包括做為系統元件(System Component)的載入器、個別的輸入法模組、文字過濾(Filter)處理模組、偏好設定應用程式等。而每個部份都是分散在各地的團隊成員分別負責,所以,有些不是我所負責的部份,我也不清楚怎麼運作,我也就只能夠分享我所知道的部份。
如果不打算使用 OpenVanilla 所提供的物件與工具,而直接打算寫一套會出現在「系統偏好設定」(System Preference)應用程式中「國際設定」(International)控制台的輸入法,像 glider 正在寫的 QIM 一樣,那就是必須從系統元件開始寫起了。在 Mac OS X 環境下,輸入法被視為是提供文字服務(T ext Service)的系統元件,系統元件程式會放在幾個地方,包括在根目錄、個人目錄、以及「系統」目錄下的「Library/Components」目錄下,系統元件是以「component」或「bundle」作為延伸檔名的Bundle目錄,例如,在「系統」目錄下的「Library/Components」目錄裡,可以找到TCIM.component以及SCIM.component,分別就是繁體中文以及簡體中文輸入法這兩個系統元件。
關於系統元件,可以參見蘋果開發者網站上的說明:[color="RED"]Introduction to Component Manager Reference,另外,其實就算在 Mac OS X下,在輸入法方面其實有很大的部份都與之前的作業系統相同,也請先參考 Mac OS 8 and 9 的[color="RED"]Text Services Manager,這篇可說是官方對於輸入法最為詳盡的技術文件。
要開始寫這樣一個系統元件的方法,就是從已經可以成功編譯的範例程式開始改寫,當然,您需要先安裝蘋果的開發工具 Xcode,這邊就不贅述了。蘋果開發者網站提供了一個輸入法範例[color="RED"]BIM(Basic Input Method),目前許多 Mac OS X 上的輸入法專案,例如之前的香草輸入法、酷音輸入法以及 MacUIM 這套日文輸入法,都是從 BIM 上開始的。而 lukhnos 之前為了重構 OpenVanilla,重新整理了一個輸入法範例,叫做 Carbon Input Method(以下簡稱 CIM),將 BIM 大幅簡化,對於寫一個輸入法元件來說,也變得比較容易一些。您可以使用 svn 取得 CIM:http://svn.openfoundry.org/carbonim/branches/0.1/ ,以下也是以 CIM 作為範例解釋。
在一個輸入法系統元件中,主要包括兩個部份,第一個部份是與系統溝通的資源(Resource),延伸檔名為「.r」,其他就是程式的部份,依據程式寫作時使用的不同語言,延伸檔名為「.c」、「.cpp」、「.m」 或「.mm」等,需要注意的是,在 Mac OS X 上,輸入法的核心部份必須以 Carbon 寫作,但是可以使用 Carbon 呼叫 Cocoa 程式。

CIM 範例中的資源檔案是 CIM.r。打開這個檔案,我們會先看到以下這一段:
[QUOTE]
resource 'thng' (128)
{
    'tsvc', /* 系統元件類別,一定要是 tsvc,代表文字服務(Text Service) */
    'inpm', /* 系統元件次類別,一定要是 inpm,代表輸入法(Input Method) */
    cimVendorCode, /* 廠商名稱 */
    0×8000+cimScript*0×100+cimLanguage, /* 輸入法的語系 */
    kAnyComponentFlagsMask, /* 一定要設定成這個數值 */
    'dlle', cimBaseResourceID, /* 其他要使用的資源 */
    'STR ', cimBaseResourceID,
    'STR ', cimBaseResourceID,
    'ICON', cimBaseResourceID,
    0×00010000,
    componentHasMultiplePlatforms,
    15872+cimScript*0×200,
    {
        0×8000+cimScript*0×100+cimLanguage,
        'dlle', cimBaseResourceID, 1000
    }
};

其中,cimVendorCode、cimLanguage、cimBaseResourceID 等變數,都定義在 CIMconst.h 裡,cimVendorCode 是用來識別廠商名稱的,必須是四個小寫英文字母,例如原本定義成 ‘lkns’,就是 lukhnos 的簡稱。另外需要注意的就是輸入法語系設定的部份,格式如範例中所寫的,必須是

「0×8000 + script code * 0×100 + language code」

script code 的資訊可在[color="RED"]這篇蘋果開發者文件中取得,Language Code則在[color="RED"]此,以繁體中文來說,Script code是「smTradChinese」,Language Code是「langTradChinese」,簡體中文則分別是「smSimpChinese」以及「langSimpChinese」。

接下來我們要看的,就是為「其他要使用的資源」部份。這邊的其他資源包括選單上的文字、輸入法顯示在使用者介面上的名稱,以及圖示。而其實在 Mac OS X 的架構下,這些東西都可以不用塞到資源檔案當中,而且在 Mac OS X 使用多國語文地方化(localization)架構下,如果將各種文字都寫死在資源裡頭,其實就枉費了 Mac OS X 的地方化架構的用意;也就是,Mac OS X會在每一個應用程式或系統元件 Bundle 的目錄中,擺放許多延伸檔名為「.lproj」的目錄,例如英文是「English.lrpoj」、繁體中文為「zh_TW.lproj」,而簡體中文則是「zh_CN.lproj」等,在這些目錄下,可以擺放各國語文翻譯的文字檔案,然後同一個應用程式,在不同的語系下,就可以以不同的語言顯示應用程式名稱以及選單文字,而至於怎樣以地方化的方式建立選單文字,容後再述。

而在 Mac OS X 的環境下,其實可以用一個獨立的tif圖檔,當作輸入法的圖示,在 10.3 系統內建的日文輸入法,以及 10.4 內建的簡體與繁體中文輸入法,都是如此,但是怎麼做?目前卻一直看不到相關的文件。所以在 OpenVanilla 中,仍然是將圖示放在資源檔案中。

在 CIM.r 當中,可以看到定義了 data ‘kcs#’、data ‘kcs4′、data ‘kcs8′,是在不同顏色數量下所顯示的不同圖示,但是其實現在所有的使用者,幾乎都是使用全彩的環境,所以大概只需要將圖示的資料放在 kcs8 裡頭即可,在這些 data 中,定義的就是一張點陣圖的每一個點陣的顏色,而其實要自己慢慢用手寫出一張點陣圖的資料,實在太辛苦了,所以還是得用一些工具。

輸入法的圖示是一張 32×32 pixel 的點陣圖,如果使用 Photoshop 繪製圖示,可以用[color="RED"]Icon Builder這類的工具,將 Photoshop 圖片輸出成圖示,記得要選擇 Classic 的格式,也就是延伸檔案為「.rsrc」的那種,而不是選「.icns」的那種,這樣便可以產生資源檔案格式的圖示檔案。接著,我們便使用 Xcode 當中的一個工具—DeRez,這個工具位在「/Developer/Tools」,是一個命令列(CLI)的程式,只要用這個工具指定你所產生的圖示檔案的路徑與檔名,便可以得到當中的訊息。例如在桌面上放了一個叫做myicon.rsrc的圖示,在終端機中,便是下以下指令:

/Developer/Tools/DeRez ~/Desktop/myicon.rsrc > ~/Desktop/myicon.txt

如此一來,桌面上就會出現一個叫做 myicon.txt 的文字檔案,只要把裡頭的內容,貼到 CIM.r 裡頭就可以了。
[/QUOTE]
 楼主| 发表于 2007-1-30 11:44:42 | 显示全部楼层
第二部分:
講完了資源檔案,接下來,我們要看的是程式的部份。我們首先要看CIMcomponent.cpp這個檔案,這個地方的程式碼,是讓你所要寫的輸入法,足成為一個系統元件的溝通介面,用來接收各種來自作業系統的事件,並且給予回應,用蘋果的官方術語來說,就是各種低階例行事件(Low- levelRoutines)。

一般來說,在進行輸入法開發的時候,幾乎都不會修改這個部份的程式碼,但是要進一步了解輸入法的運作,就必須要解說,一個輸入法系統元件會處理那些事件,才能夠知道我們要在什麼時候,讓輸入法做什麼事情。您也可以先參看蘋果的這幾篇官方文件:[color="RED"]Text Services Manager Reference: Low-levelRoutineSelectors,以及[color="RED"]Component Manager Reference: Request Codes
在CIMComponentDispatch這個地方,可以看到使用case、switch語法處理各種事件,這些事件包括:
[QUOTE]
    * 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 中並沒有使用前述[color="RED"]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 的寫法。

[/QUOTE]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2007-1-30 11:56:28 | 显示全部楼层
第三部分:
到目前為止的介紹所介紹的,都只是一個輸入法系統元件的「外殼」,而還沒有介紹作業系統如何與輸入法的核心演算法溝通。接下來要介紹的,就是輸入法如何取得使用者輸入的按鍵的資訊,然後給予適當的回應。

作業系統會將使用者按了某個鍵盤按鍵這樣的事情,當成是一種「事件」(Event),而在 Mac OS X 中,有非常多不同種類的事件,包括滑鼠事件與鍵盤事件的類型,而每個類型下又有許多更進一步的區別,例如,滑鼠事件就有滑鼠移動、點選、拖拉、放開,鍵盤則有打一下、按著不放…等等,蘋果在 [color="RED"]Carbon Event Manager Reference 這份文件中,便完整列出了各種事件。輸入法則透過前一篇介紹的 kCMTextServiceEvent,獲得這些事件,以 CIM 這個範例來說,可以在 CIMcomponent.cpp 裡頭,在遇到 kCMTextServiceEvent ,就丟到 CIMSessionEvent() 裡頭處理, CIMSessionEvent() 則寫在 CIM.cpp 裡頭。

在 CIMSessionEvent() 這裡,可以先看到:

[QUOTE]  
if (GetEventClass(evnt)!=kEventClassKeyboard ||
        !(eventkind==kEventRawKeyDown || eventkind==kEventRawKeyRepeat))
        return FALSE;

這一段程式的意義在於篩選我們所需要的事件類型,輸入法其實需要的就是只有鍵盤事件而已,所以我們只需要 kEventClassKeyboard (代表鍵盤事件這個類別)。而在鍵盤事件中又有很多種,我們只需要「打一下」(kEventRawKeyDown)還有「按著不放」(kEventRawKeyRepeat)兩個項目,其他的事件就都不要了,您可以在[color="RED"]這裡看到所有的鍵盤事件列表。不過,如果您要做手寫辨識這類不是透過鍵盤的文字輸入,您可能就需要偵測滑鼠事件,但是就 OpenVanilla 專案來說,並沒有做過這樣的嘗試,也就無法解說了。

然後可以看到這幾行:
UInt32 keycode, modifiers;
    unsigned char charcode;

    GetEventParameter(evnt, kEventParamKeyCode, typeUInt32, nil,
        sizeof(keycode), nil, &keycode);
    GetEventParameter(evnt, kEventParamKeyMacCharCodes, typeChar, nil,
        sizeof(charcode), nil, &charcode);
    GetEventParameter(evnt, kEventParamKeyModifiers, typeUInt32, nil,
        sizeof(modifiers), nil, &modifiers);

我們現在要用[color="RED"]GetEventParameter(),從傳入的事件中,過濾出我們所需要的資料。從前面三行程式,我們分別取得 kEventParamKeyCode 、kEventParamKeyMacCharCodes 以及 kEventParamKeyModifiers,分別存放在 keycode 、 charcode 以及 modifiers 裡頭。

其中 charcode 就是使用者所輸入的按鍵,那麼 keycode 又是什麼呢?—這就是有時讓人不禁痛恨蘋果的地方,在蘋果的開發者文件當中,根本沒有對於這些參數的意義給予完整的解釋,而在 CIM 的範例中,其實只有使用到 charcode 而已,而這樣的寫法基本上完全都是繼承 BIM 的範例而來,在取得了 charcode 之後,可以看到我們把後續工作,都丟進 CIMCustomHandleInput() 處理…而您的輸入法的各種演算,會從這個地方開始,而從這個地方開始,也都要看您自己的了。

需要注意的是(其實,在這邊提到「需要注意的是」這樣的句子,都是過去一年來花了很多時間 debug 才了解的心得),Mac 的鍵盤與其他的系統略有不同,例如在各種筆記型電腦上,都可以看到鍵盤上除了有 Return 按鍵之外,另外還有一個 Enter 鍵,通常來說,兩個按鍵做的都是同樣的事情,也始終搞不清楚為什麼要區分成兩個按鍵,但是在輸入法的部份來說,會偵測到兩個不同的按鍵,一是 0×03,一是 0×0D,另外就是在外接鍵盤上,除了 Backspace 之外,還有一個 Delete 鍵,也會分別送出 0×08 以及 0×7F,如果某些按鍵並沒有指定定義的話,是會造成問題的。

另外就是快速鍵,所有的快速鍵都表列在[color="RED"] Event Modifier Bits 這份文件中。需要特別注意的是,在這份表格中並沒有列出 num lock 這個快速鍵,如果您對於在設定了 num lock 後,在數字鍵盤上做一些比較特別的行為,那,num lock 的 modifier 的名稱為 kEventKeyModifierNumLockMask 。
[/QUOTE]

[color="RED"]lukhnos(OpenVanilla的另外一个作者)的留言:
其實 keycode 是什麼,在以前的 Inside Macintosh (yes, 都是 pre-OS X 時代的東西) 應該都找得到。只是隨著 Apple 不斷 phase out 掉所有的舊文件,輸入法就越來越像「KK 音標」這種孤島效應的產物,離 OS X 核心越來越遠,所有的支援文件跟 debug 方法都消失了。

附帶一提:Apple 竟然完全沒有任何文件提到 Cocoa 的繪圖座標怎麼跟 Carbon (QuickDraw) 繪圖座標做轉換。OpenVanilla 的游標定位那段程式,是參考很多網路上不知哪來的、別人程式碼裡的做法,才知道「喔,原來是這樣轉換的」,而且還沒有 API 可用咧。很離譜吧?!

glider(QIM的作者)的留言:
我已经找到了不用在.r文件里使用原始的icons信息,而直接通过.icns文件来做为输入法的图标的办法;QIM FIX 4就是这样做的,其实很简单.

1.准备一个icns图标文件16X16
2.拷贝到输入法的Resources目录
3.在输入法的Info.plist添加一个tsInputMethodIconFile键,值为string类型的对应的icon文件名;

完成了.
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表