本文簡述 xcin-2.5 的設計理念與整個程式架構。

xcin-2.5 的設計理念為:

1. 輸入法各自獨立且模組化。

2. 使用者與往常一樣，只要準備好一個 .cin 檔，在 xcin 下就能馬上使用，多半
   不需要為此輸入法多寫一個模組。

3. 輸入法全然扮演一個類似 "server" 的角色，也就是 xcin 向輸入法提出請求，
   而輸入法則根據請求做出回應。除此之外，輸入法不參與 xcin 其他部分的活動。

4. xcin 完全按照 locale, XIM 的架構設計，儘可能朝標準化的方向走，希望從此
   能完全解決 X window 下的中文輸入問題。

基於這些理念，使得 xcin-2.5 與舊版的 xcin 幾乎已完全不一樣，其基本架構如下:

                       IC manager <------+
		           ||	         |
		           ||	         |
	               XIM system <------+
		           ||	         |
		           ||	         |
	               GUI system     module <----- cinput
		           ||	         |
		           ||	         |
                        xcin_core <-- xcintool
			   ||		 |
			   ||		 |
		         xcin_rc  <--  siod 

以下就上圖的各部分做簡單的說明。


A. xcintool: (include/xcintool.h, lib/xcintool/*.c)

   這是 xcin 最底層的工具函式庫，它只負責一些簡單而基本的操作。

B. siod & xcin_rc: (lib/siod/*, lib/xcintool/xcin_rc.c)

   這是 xcin 的 rcfile 讀取系統。其公用函式呼叫介面與 xcintool 連結在一起，
   而其核心則為 siod lib 。 siod 為一個混合 lisp 與 Scheme 語言的直譯器，這
   使得 rcfile 的設定內容可以採用嚴緊而富彈性的 lisp / Scheme 語言。

   xcin_rc 系統可以同時提供 xcin_conf 與 module 讀取 rcfile 資料之用，同時
   也可以做為 xcin 其他外部程式 (如 cin2tab) 共通的 rcfile 讀取介面。故我們
   只需要一個 rcfile 即可做好所有的 xcin 相關設定。

   所有經由 siod 與 rcfile 讀取系統所讀入的全域資料變數 (不包括 CINPUT 內的
   細部設定) 全部儲存在 xcin_rc 資料結構中，當 xcin 有任何一個 module 啟動
   並開始初始化時，此結構便會傳入該 module 的初始化函式中，則該 module 便可
   以參考該結構中的資料做必要的設定動作。

C. xcin_core: (include/constant.h, include/xcin_core.h, xcin_main.c)

   這是 xcin 主程式資料結構，它是由早期 clkao 兄所規劃的 xcin_core module
   轉變過來的。它負責 xcin 啟動時一切的初始化動作，包括讀入命令列選項、設
   定 locale 、執行 rcfile 讀取系統讀取 rcfile 、進行 xcin 其他部分的初始
   化 .... 等等，同時它的資料結構還保有目前 xcin 所有的狀態資訊，包括 GUI、
   XIM、以及輸入法狀態等。此資料結構為 xcin 主程式所私有，由 xcin 核心直接
   控制，輸入法模組無法參考其內容。

   此資料結構中， xcin_core.xcin_mode 用來記錄 xcin 的狀態，其作用有二:
   一是記錄來自 rcfile (xcinrc) 的設定組態，另一個是記錄 xcin 執行時期的
   狀態。目前已定義了以下的 bits:

   XCIN_MODE_HIDE:  如果 rcfile 中設定 XCIN_HIDE YES 則這個 bit 會設為 on。

   XCIN_SHOW_CINPUT:  如果 rcfile 中設定 DEFAULT_IM_SINMD 值為 "DEFAULT",
	則表示使用者希望在切換輸入法時，輸入法字鍵顯示也跟著切換，則這個
	bit 會設為 on。但如果使用者按了 ctrl+alt+i 切換輸入法字鍵顯示時，
	則這個 bit 會自動設成 off。

   XCIN_XKILL_OFF:  如果 rcfile 中 XKILL_DISABLE 為 YES 時則為 on。

   XCIN_IM_FOCUS:  如果 rcfile 中 IM_FOCUS_ON 為 YES 時則為 on。

   XCIN_MODE_WORKING:  這是在 XCIN_MODE_HIDE 設為 on 時才會有作用。這時如
	果 xcin 視窗出現時，此 bit 會設為 on, 若隱藏時會設為 off。

   XCIN_RUN_IM_FOCUS:  記錄目前 xcin 是否處在中文輸入的 IM Focus 狀態 (IM 
	Focus 的 切換鍵為 ctrl+alt+f)。

   XCIN_RUN_2B_FOCUS:  記錄目前 xcin 是否處在全形輸入的 IM Focus 狀態 (IM
	Focus 的 切換鍵為 ctrl+alt+f)。


D. module: (include/module.h include/imodule.h include/cinput.h module.c)

   這是 xcin 的輸入法模組核心，所有的可動態載入輸入法都是經由這裏進入 xcin
   系統。此核心維護著所有輸入法的相關資料，讓 xim_conf 系統取用。有關其詳細
   細節請參考 module 一文。


E. GUI system: (include/xcin_core.h, gui.c)

   這是 xcin 的 GUI 系統的核心，所有的 xcin 視窗繪圖與操作都是在這裏完成，
   而其資料結構則存於 xcin_core 裏頭。 xcin 的視窗規畫定義如下:

		+-------------------------------------------+
		|                     a                     |
		+---------+---------+--------------+--------+
		|    b    |    c    |      d       |   e    |
		+---------+---------+--------------+--------+

   a. 輸入法多重字顯示區。

   b. 輸入法名與狀態顯示區 (如 [內碼][半形])。

   c. 輸入法組字區。

   d. 輸入法組字顯示區，可用做完成組字後，顯示該字完整的字鍵碼。

   e. 輸入法英文名顯示區 (如 zh_hex, phone 等)。

   當 rcfile 中某輸入法的 SINMD_IN_LINE1 設為 "YES" 時，則原本在 d 的輸入
   法組字顯示會改在 a 區中顯示。

   GUI 系統在初始化時，是自 xcin_rc 取得 rcfile 的資料，而其繪圖與視窗操作
   函式主要有三個:

   void win_draw(void):
	視窗繪圖。而其繪圖的內容 (包括上述 5 個區應顯示什麼字串) 都直接
	由 xcin_core 資料結構取得。

   void win_map_change(int state):
	用於視窗的顯示與隱藏切換。這是當 rcfile 中設定 xcin 隱藏時才會被
	呼叫。

   void win_draw_map(void):
	此函式用來同時呼叫 win_draw() 與 win_map_change() 兩個函式。而且
	它會根據 xcin_core.xcin_mode 的及其他狀態來判斷是否呼叫
	win_map_change() 來顯示 / 隱藏 xcin 視窗。一般而言我們都是直接使
	用此函式來做 xcin 視窗繪圖工作，而不直接使用上頭那兩個函式。

   GUI 系統是在以下兩種狀況下才進行繪圖的，一是接到 Expose event, 另一是
   當任何輸入法狀態改變時。所謂輸入法狀態改變，包括切換輸入法、敲入任何
   有意義的字鍵等等。這時 GUI 系統會跟據 xcin_core.winchange 變數的值來判
   斷是否有任何輸入法狀態的改變，因此當 XIM 系統若查覺有輸入法狀態改變時，
   只要改變這個值，即可將結果反應在 xcin 視窗上。 winchange 狀態定義如下:

   WIN_CHANGE_IM_CONTENT:  當這個 bit 為 on 時，表示輸入法狀態只有小符改
	變，例如使用者敲入一個有意義的字鍵。

   WIN_RAISE:  當這個 bit 為 on 時， win_draw_map() 會在完成繪圖後，將
	xcin 視窗變為第一個視窗。

   WIN_CHANGE_IM:  當它為 on 時，代表上述所有的 bits 皆為 on, 意思是輸入
	法狀態已大符改變，通常是在輸入法切換的情況下發生的。

   由於 GUI 系統在畫圖時是根據 xcin_core 中的輸入法狀態資料來畫的，而這
   些狀態資料是來自於 xcin_core.ic 結構，即目前工作中的 IC (請見後頭的說
   明)。因此，凡是反應在 xcin_core.ic 中的改變，會即時反應在 GUI 系統上。
   如果只是小改變，即 WIN_CHANGE_IM_CONTENT 為 on 時， GUI 系統就直接根
   據改變來繪圖。但如果是切換輸入法，或變換工作 IC 時，就必須要重新設定 
   gui_conf 中的相關指標與資料，以指向正確的 IC 位置。而這個工作是交由 
   static void inpstate_content(inp_state_t inp_state) 函式來達成，它是
   由 win_draw() 函式來呼叫的。當 win_draw() 發現 WIN_CHANGE_IM 為 on 時，
   才會呼叫 inpstate_content() 來重設資料。

   除此之外， GUI 系統的繪圖模式有很多可以藉由輸入法與 XIM client 的溝
   通介面: IC 的 inpinfo (見後述與 module 一文的說明) 組態來改變，如此
   可以藉由 GUI 繪圖的改變，而充份反應目前輸入法所處的狀態。
   
   當 xcin 完成所有部分的初始化動作時，它就會進入 GUI 系統的 gui_loop()
   函式迴圈持續執行，直到 xcin 結束為止。它的工作是等待下一個 X event
   (XNextEvent()), 並把該 X event 交給 XFilterEvent() 來處理。如果它是
   來自於 X apps 的 XIM event (即 X apps 進行中文輸入等動作) 時， Xlib 
   會將此 event 傳給 XIM 系統來處理 (請見後頭的說明)。若不是這類的 X 
   event, 才交由 gui_loop() 進行其他的動作 (如重畫視窗) ，然後週而復始。

   為了讓程式簡單起見，我目前只有開一個 Window, 即 xcin 主視窗，並沒有
   再開其他的附屬 Window。


F. XIM system (include/xcin_core.h, xim.c, fkey.c, lib/IMdkit)

   這是處理 XIM 協定的核心，也是所有來自 module 系統的輸入法與 X apps 的
   溝通介面。每一個使用 XIM 協定、可接受 xcin 輸入的 X window 視窗在開啟
   後，都會送一個訊號到這裏，而 XIM 系統便產生一個 IC (Input Context) 給
   它。 IC 記錄著目前輸入法的狀態與相關的 XIM 協定資料。

   本系統並沒有直接與 Xlib 溝通，而是透過 IMdkit lib 與 Xlib 溝通。 IMdkit
   並不是我們所發展的，它是 yhsiao 兄與 gamete 兄介紹給我們的，它可以處理 
   Xlib 底層的複雜資料結構與協定，讓我們在使用 XIM 協定的過程可以簡化。在
   初始化過程中，我們利用它的函式向 Xlib 註冊 xcin 的相關資料，讓之後啟動
   的其他 X apps 知道有 xcin 這一個 XIM server。所註冊的資料重要的有:

   IMServerName:  我們的 XIM server 名。若在 zh_TW.Big5 locale 環境下啟動
	xcin 的話, 則 IMServerName 便會設為 "xcin", 否則的話會設為 
	"xcin-<locale name>"。

   IMLocale:  我們所使用的 LC_CTYPE locale。

   IMInputStyles:  xcin 支援的輸入模式。目前 xcin 只支援
	XIMPreeditNothing|XIMStatusNothing 這種輸入模式，意思是輸入法組字
	區與輸入法狀態區都在 xcin 視窗內 (即 input_style 為 "Root")。未來
	我們會再加入其他支援。

   IMProtocolHandler:  XIM event 處理函式，此為 XIM 處理中心。

   IMOnKeysList:  註冊 trigger keys。 xcin 採用 XIM 動態連線的模式，也就是
	當 X apps 在做英文 / 半形輸入時，使用者敲入的字鍵不會送入 xcin, 直
	到他按下 trigger keys 切換成中文或全形輸入時， Xlib 才會將使用者敲
	入的字鍵送給 xcin 處理。 xcin 註冊的 trigger keys 計有: 

	中英切換: 預設為 ctrl+space

	全形半形切換: 預設為 shift+space

	輸入法切換: 預設為 ctrl+shift, shift+ctrl, 及 ctrl+alt+[0123456789-=]

	快速片語輸入鍵: 預設為 shift+alt+<ascii key>

	上述的 trigger keys 都可以藉由 rcfile 的設定而改變，且交由 fkey.c
	維護。它會判斷使用者設定的 trigger keys 是否合法，並盡量避免衝突的
	發生。


   int im_protocol_handler(XIMS ims, IMProtocol *call_data) 函式是 xcin 註
   冊的 IMProtocolHandler ，所有來自 Xlib (IMdkit) 的 XIM event 全部都會送
   到這處理，其中 call_data 是這些 event 的內容。而這些 event 其時是來自於 
   GUI 系統中 gui_loop() 函式所呼叫的 XNextEvent(), 經由 XFilterEvent() 過
   濾，如果發現是屬於 XIM event, 則直接經由 IMdkit 送到這裏，而不再送回給 
   gui_loop() 函式。 XIM event 計有:

   XIM_OPEN:  當一個 X app 剛啟動，並且決定使用 xcin 做為 XIM server 時，
	它就會送這個 event xcin。

   XIM_CLOSE:  當一個 X app 要結束前，它應該要送這個 event 給 xcin 通知說
	要做一些清理的動作。

   XIM_CREATE_IC:  當一個 X app 啟動後，它每開一個使用 XIM 的視窗，就會送這
	個 event 過來。這時候 xcin 必須為它產生一個 IC 以儲存相關資料。

   XIM_DESTROY_IC:  當 X app 某個使用 XIM 視窗要關閉前，它應該要送這個 
	event 給 xcin 以通知 xcin 做一些繕後工作。這時 xcin 可以將屬於
	此視窗的 IC 清理掉。

   XIM_SET_IC_FOCUS:  當滑鼠點選某個使用 XIM 的 X app 視窗時，該視窗就會送
	這個 event 給 xcin。這時 xcin 就必須進行切換 IC 的工作。

   XIM_UNSET_IC_FOCUS:  當滑鼠點選其他的 X 視窗時，原先正工作中的使用 XIM 
	視窗就會送這個 event 給 xcin, 然後才離開 focus。

   XIM_TRIGGER_NOTIFY:  當原本的 X app 工作視窗沒有與 xcin 連線 (即處於英
	數 / 半形輸入) 時，如果要進行中文 / 全形輸入，則當使用者按下
	trigger key, IMdkit 便會送這個訊號給 xcin 。這時 xcin 必須進行為
	此 IC 進行初始化的動作。

   XIM_FORWARD_EVENT:  當使用者按下 trigger key 後，他往後在此視窗中敲入
	的每一個字， xcin 都會接到這個 event, 同時 xcin 也會從 call_data
	的其他內容中知道他按了那些按鍵，這時 xcin 會根據這些按鍵進行以下
	的處理:

	1. 是不是 trigger keys? 使用者可能在輸入中途切換輸入法，或恢復英
	   文輸入等。如果是 trigger key, 則 xcin 會進行輸入法切換動作。

	2. 若不是 trigger keys, 則 xcin 會將字鍵傳給目前輸入法的 keystroke()
	   函式中，同時也將目前的輸入法狀態資料 inpinfo_t *inpinfo 傳送給
	   它，讓它進行判斷與組字工作。然後 xcin 會根據它的傳回值決定下一
	   步的行動。

	3. 若輸入法的 keystroke 函式對於這個鍵沒有興趣，但目前是處於全形
	   輸入狀態，或者說 keystroke 函式希望全形輸入法來處理這個字鍵，
	   則 xcin 會將這個字鍵傳給全形輸入法模組處理。

	4. 若輸入法的 keystroke() 函式或全形輸入法模組完成組字，並希望組
	   出的字回傳給 X app 時， xcin 會將這個字回傳給 X app。

	5. 若輸入法的 keystroke() 函式或全形輸入法模組表示這個字鍵對它們
	   而言都沒有意義，則 xcin 會將這個字鍵還給 Xlib, Xlib 再將字鍵還
	   給 X app。

   XIM 系統的相關資料結構中，最重要的是 xcin_core.ic, 它是一個指向目前工作
   中的 IC 的位址。


G. IC manager (include/IC.h, xim_IC.c)

   這個部分實際上與 XIM 系統是一體的，它負責維護所有產生的 IC 串列。 IC
   是一個很複雜的資料結構，它記錄了目前這個工作視窗的輸入法狀態。其定義
   如下 (部分)

===========================================================================
typedef struct _IC IC; /* forward declaration */
struct _IC {
    CARD16              id;             /* ic id */
    CARD16              connect_id;     /* id of connected client */
    time_t              exec_time;      /* recent excution time. */
    struct _IC         *next;
    inp_state_t         inp_state;      /* ic cinput state */
    inp_state_t         inp_num;        /* ic cinput num */
    inp_state_t         sinp_num;       /* ic cinput num (sinmd) */
    imodule_t          *imodp;          /* current binding cinput module */
    imodule_t          *s_imodp;        /* show keystroke cinput module */
    inpinfo_t           inpinfo;        /* inp info referenced by gui */
    unsigned int        skey_size;      /* sinmd_keystroke buf size. */
    wch_t              *sinmd_keystroke;/* for keystroke of a published cch. */
    unsigned int        cch_size;       /* cch buf size. */
    char               *cch;            /* composed char for commit. */

/*
 *  Set from: XICAttribute *ic_attr, *pre_attr, *sts_attr;  
 *  ic_set_value_by_name();
 */
    INT32               input_style;    /* input style */
    Window              client_win;     /* client window */
    Window              focus_win;      /* focus window */
    char               *resource_name;  /* resource name */
    char               *resource_class; /* resource class */
    PreeditAttributes   pre_attr;       /* preedit attributes */
    StatusAttributes    sts_attr;       /* status attributes */
};
===========================================================================

   目前由於 xcin 只支援 XIMPreeditNothing|XIMStatusNothing 這種輸入模式，故
   INT32  input_style 以下的欄位目前都沒有作用。而以上的各欄位意義為:

   id:  此 IC 的編號。

   connect_id:  使用此 IC 的 X app 編號。

   exec_time:  此 IC 最近的使用與執行時間。這是用於 garbage collection 用。
	詳見以下的說明。

   inp_state:  此 IC 的輸入法狀態，它的值可以是:
	IC_CINPUT:   若為 on 表示中文輸入，否則為英數輸入。
	IC_2BYTES:   若為 on 表示全形輸入，否則為半形輸入。
	IC_XIMINIT:  若為 on 表示此 IC 已執行過 modules->xim_init() 函式了。
		     同時可用於判斷是否應執行 modules->xim_end() 函式。
	IC_XIMFOCUS: 若為 on 表示此 IC 已執行過 modules->switch_in() 函式了。
		     同時可用於判斷是否應執行 modules->switch_out() 函式。
	IC_NEWIC:    若為 on 表示此 IC 是新產生的，藉此可用來判別並進行進一
		     步的 XIM 動作。 

   inp_num:  此 IC 目前使用的輸入法編號，即輸入法的 setkey 號碼。

   sinp_num:  目前用來顯示輸入法字鍵碼的編號。

   imodp:  指向目前使用中的輸入法模組。

   s_imodp:  指向目前使用中的「輸入法字鍵碼顯示」的模組。

   inpinfo:  目前的輸入法狀態資料。詳見 module 一文的說明。

   skey_size:  sinmd_keystroke 緩衝區的大小。

   sinmd_keystroke:  用來存放「輸入法字鍵碼顯示」的緩衝區。此緩衝區的大小
	由 xcin 視需要自行調整，各輸入法模組不需要為此而傷腦筋。

   cch_size:  cch 緩衝區的大小。

   cch:  用來存放 commit 給 XIM client 的字串。此緩衝區的大小由 xcin 視需
	要自行調整，各輸入法模組不需要為此而傷腦筋。

   除此之外，當未來 xcin 加入其他的輸入模式時， xim_IC 將負責進行更複雜的
   IC 設定機制。


   IC garbage collection:

   由於並非所有的 XIM client 在結束時都會送 XIM_CLOSE 或 XIM_DESTROY_IC
   給 xcin, 這會造成 xcin 所開啟使用的 IC 會逐漸累積，越來越多。再執行效
   率與程式複雜度的考量下，同時也考慮了 racing condition， 故 xcin 目前
   採用以下簡單的 garbage collection 機制:

   xcin 大至上每隔 5 分鐘做檢查一次所有的 IC, 以確定其所屬的 client 還活
   著。之所以用「大至上」這個字眼，是因為我沒有用 multi-thread 或 fork 
   另一個 process 來精準控制時間，而是直接將檢查函式插入 
   im_protocol_handler(), 這個函式是最常被呼叫的，諸如在 client 按任一
   鍵、或甚至用滑鼠點選 client, 都有可能會呼叫到這個函式。因此，只要我
   們持續使用任何一個 client, 就會不停地進入 im_protocol_handler()，故
   在此直接安插檢查函式，應該是最簡單也最有效的辦法。

   為了顧及 racing condition, 我在 IC 結構中多加了 time_t exec_time 這
   個 variable, 每當這個 IC 被使用時， exec_time 就會被設入目前的時間，
   而檢查函式最後只會挑超過 10 分鐘以上 idle 的 IC 進行檢查，如果發現該
   IC 所屬的 window 不在了，就將此 IC 做資源回收。



T.H.Hsieh
