[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[thhsieh: xcin-2.5 & OverTheSpot]



----- Forwarded message from thhsieh -----

Date: Fri, 18 Feb 2000 12:13:38 +0800
From: thhsieh
To: xcin@linux.org.tw
Subject: xcin-2.5 & OverTheSpot
X-Mailer: Mutt 1.0pre3us

Very sorry, 本來打算昨天晚上 post 的,結果直到現在才寫好,請大家參考。

Sorry again, 這真是篇又臭又長的文件,如果您對 OverTheSpot 沒興趣,請直
接跳過好了,否則的話,我很希望您能花一點工夫看一下,這對於我們 developemnt
應該是有幫助的。 :-))

晚一點我會再 post 專文討論 GUI Request API。


T.H.Hsieh

========================================================================

本文討論 xcin 與 OverTheSpot input style, winlist 架構,以及目前遭遇
的困鞋、有待完成的部分等等。

1. 什麼是 OverTheSpot?

   這一段開場白是給不曉得這是什麼的朋友看的,已經知道的就直接跳過好了 :-))
   可能寫得不好,也請大家多多指點 :-))

   在 XIM 的協定中,有一個稱之為 input style 的「協定」,簡單講就是
   XIM server 與 XIM client 之間要以怎麼樣的使用者介面展現給使用者。
   XIM 支援的 input style 有很多種,但大體上可以分成 pre_edit area
   (組字區) 與 status area (輸入法狀態區) 來談: 這兩個區域可以分別
   (或同時) 在輸入法視窗上、或出現在您目前的游標附近、或 XIM client
   自己準備一塊區域來顯示 .... 等等。至於要用那一種 style? 通常都是
   當 XIM client 與 server 搭上線時,它先向 server 詢問有那些 style
   可用,然後再從中挑選一種,告訴 server 說指定要使用,二者達成協議
   後即可上路。

   在 xcin-2.5.2-pre1 之前, xcin 只支援一種 input style, 傳統上稱之
   為 Root, 意思是 pre_edit area 與 status area 都在 xcin 視窗中:前
   者即為視窗第二行那塊紅棕色的區域 (您所敲入的字鍵碼會依續出現,等
   到敲完之後才會跑出一個完整的字,這過程就是「組字」) ,若遇到多重
   字選擇時還會包括視窗的第一行;而後者則為視窗第二行的左右兩塊區域,
   它們顯示了目前 xcin 所處的狀態。

   而自 2.5.2-pre1 起,我們嘗試替 xcin 加入另一種 input style,傳統上
   稱之為 OverTheSpot, 它的意思是 pre-edit area 會出現在您目前打字的
   游標附近,而且是由 XIM server (即 xcin) 另外在那游標附近開一個小小
   視窗,而將 pre_edit area 中的資料顯示在那。此 input style 最大的好
   處就是 pre_edit area 是跟著游標跑的,如此當您在打字時,您就不用眼
   睛一直移上移下看游標以及 xcin 視窗了。


2. 如何啟動 OverTheSpot?

   原則上, xcin 還是以 Root input style 為主,預設是不啟動其他 style
   的。如果您要啟動 OverTheSpot, 必須修改 xcinrc 檔:

(define INPUT_STYLE             '(Root OverTheSpot))

   如此當您在重新啟動 xcin 時,您會見到如下的訊息:

XCIN (Chinese XIM server) version xcin 2.5.2-pre1.
(module ver: 20000110, syscin ver: 20000210).
(use "-h" option for help)

xcin: XIM server "xcin" locale "zh_TW.Big5" transport "X/"
xcin: inp_styles: Root OverTheSpot
		  ^^^^^^^^^^^^^^^^
   這樣就表示成功啟動了。然而,是否接下來就使用 OverTheSpot 來輸入,還
   得由 XIM client 來決定 (目前 xcin 不主動為 client 決定), client 還
   是可以指定使用 Root 來輸入。但由於 OverTheSpot 是較好的一個 style, 
   因此目前絕大部分的 XIM client 在對於使用優先順序上都是 OverTheSpot
   大於 Root (通常 Root 都是排在最後的)。因此,如果您在 xcin 啟動它之
   後,在一般情況下 XIM client 都會指定使用它。


3. 麻煩大了:

   現在有一個很大的問題: 在 XFree86-3.3.X 上 xcin 似乎完全不能使用 
   OverTheSpot, 我們做過一些測試,若將 xcin 的 OverTheSpot 啟動後, XIM 
   client 一見到有它可以用,就很高興地指定要用,但就在指定的那一剎那 
   (client 呼叫 XCreateIC 時) , xcin 卻再也無法接收來自 client 的訊息,
   而 client 就直接 XCreateIC 失敗 (傳回 NULL)。這看起來有點像是, client
   的訊息本來是要經過 Xlib 送達 xcin, 卻在中途失敗,彈了回來,因此就無
   法使用 OverTheSpot。

   這會有什麼後遺症呢?除了不能用 OverTheSpot 以外,以 rxvt-2.7.x 為例,
   XCreateIC 一失敗,它就主動與 xcin 中斷連繫,這樣一來,就等於連 Root
   style 都不會去用,也就等於完全無法輸入中文。

   當然,這也許是 rxvt 設計不夠完善,如果要完善一點,也許可以設計成: 第
   一個 style 指定失敗,就指定第二個,直到成功為止。但我擔心,可能沒有多
   少個 XIM client 有考慮到這些。例如 Netscape, 也和 rxvt 一樣, OverTheSpot
   一失敗就直接與 xcin 斷訊了。

   因此,就目前情況下啟動 xcin 的 OverTheSpot 反而會陷入很糟糕的問題,既
   然絕大部分的 client 預設情況下都會指定使用 OverTheSpot, 偏偏在 
   XFree86-3.3.X 下 xcin 無法使用 OverTheSpot, 再來就是一但指定失敗,那些
   client 就直接與 xcin 斷訊了,這就變成了: 在預設情況下大部分的 client
   都無法接受來自 xcin 的輸入。

   因此,在問題未克服之前,若您的系統跑的是 XFree86-3.3.X, 請不要啟動 xcin
   的 OverTheSpot。

   然而, PhantomCat 兄曾經向我回報: 在 XFree86-3.9.X (即 XFree86-4 的
   pre-release) 下 xcin 可以使用 OverTheSpot。因此,如果您的系統是使用
   XFree86-3.9.X 的話,您不妨可以試試,看是否正常,並請您向我們回報。

   但奇怪的是,若使用其他的 XIM server (例如韓國的 ami), 它們的 OverTheSpot
   在 XFree86-3.3.X 下可以跑得很好。我也比較過它與 xcin 的程式碼,發現
   在處理 XIM 的部分大同小異,因此我不禁有點懷疑是不是 XFree86-3.3.X
   的 XIM 或 Xi18n 的部分,在我們的 locale (zh_TW.Big5) 下有些地方會有
   問題 (記得前一陣子大家才在 CLE 及 Debian 中 patch lcGen 的部分嗎)?
   如果是的話,表示 Xlib 還需要進一步 patch, 這就必須再 trace Xlib 原始
   碼才會知道了。


4. Winlist 架構:

   因為我的機器還是跑 XFree86-3.3.6, 因此在寫 OverTheSpot 部分時,有點半
   瞎子摸象在做,故寫出來的部分可能有問題,而且沒有寫完整,這需要使用
   XFree86-3.9.X 的朋友來幫忙補齊。但我可能要先提一下目前的程式架構,
   好讓有興趣幫忙的朋友容易 follow。

   先前 PhantomCat 兄已寄給我許多 xcin + OverTheSpot 的修正 (再一次謝謝
   他 :-)),而在這一版中我參考了他的修正,將他的 code 整合到 xcin 的
   winlist 中。 xcin 處理 GUI 部分的 source 為:

	include/gui.h		(definition of GUI and winlist data structer)
	gui.c			(GUI over all structer / tool functions)
	gui_main.c		(xcin main window)
	gui_menusel.c		(GUI Request Menu Selection window)
	gui_overspot.c		(OverTheSpot candidate window)

   就 OverTheSpot 而言,其 GUI 的部分就全部集中在 gui_overspot.c 裏頭,
   而這也是我尚未完成的地方。 gui_overspot.c 的內容如下 (其實 gui_main.c
   與 gui_menusel.c 也是類似):

winlist_t *
gui_overspot_init(gui_t *gui, winlist_t *main_win, IM_Context_t *imc)

   它用來產生 OverTheSpot 所需的視窗,並填好 winlist 的結構 (見 gui.h):

struct winlist_s {
    Window window;			/* The corresponding window */
    unsigned int wid, imid, reqid;
    byte_t winmap;			/*  1: window is mapped
					    0: window is unmapped
					   -1: window is destoryed */
    int pos_x, pos_y;			/* The position of the window */
    unsigned int width, height;		/* Window size: in unit of pixels */
    unsigned int c_width, c_height;	/* Window size: in unit of English 
					   characters */
    void *data;				/* Data for window drawing */
    void (*win_draw_func)(gui_t *, winlist_t *);
				/* Function to draw the window */
    void (*win_attrib_func)(gui_t *, winlist_t *, XConfigureEvent *, int);
				/* Function when XConfigureEvent received */
    void (*win_destroy_func)(gui_t *, winlist_t *);
				/* Function to destroy window */
    winlist_t *next;
};

   其中 wid 是此 Window 的 type, OverTheSpot 應設為 WID_OVERSPOT。 imid
   是目前此 window 相當應該 IMC 號碼 (見後述), reqid 在這裏不需要用到,
   那是給 GUI Request window 用的,故在此我們不管它。

   當您要 Map window (顯示視窗) 與 Unmap window (隱藏視窗) 時,請呼叫
   gui_winmap_change() 函式 (定義在 gui.c), 它會連同 winmap 的值一起
   更新,同時要注意,如果 winmap 的值是 -1 時,請不要再對它做任何畫圖
   或 change map 的動作,因為那表示該 window 已被 destroy, 不存在了。

   以下三個 function 分別是:

	win_draw_func:  用來畫視窗用的,如果程式運作中需要顯示或隱藏視
			窗時,請在此呼叫 gui_winmap_change。而用來畫視
			窗所需的資料就在 data pointer 所指位置。
	win_attrib_func:  當此視窗接到 XConfigureEvent 時 (如視窗被滑鼠
			拖動,視窗被放大縮小 .... 等),這個函式就會被呼
			叫,這時我們就可以用它來設定視窗新的 pos_x, pos_y,
			width, height, etc.
	win_destroy_func:  當此視窗被 destroy 時會被呼叫,這時我們可以用
			它來做一些收尾的動作 (如果需要的話)

	其中 win_attrib_func 與 win_destroy_func 可以設為 NULL, 則 xcin 
	會以預設的方式來處理。

   就 OverTheSpot 而言,我希望它的視窗是可以跟著輸入的游標跑,也就是說
   它必須隨時改變位置,而且完全不受 Window Manager 的控制。因此,我用了
   XChangeWindowAttributes() 來讓它擺脫 WM 的控制,如此,我們就無法用滑
   鼠來移動它的位置、改變它的大小、以及將它關閉了。

   而要讓它隨著游標移動,它必須隨時知道游標目前的位置,其位置是從 IMC 
   structer 的 spot_loc 傳入的 (見後述) ,因此,在其 win_draw_func 中,
   它必須每次都檢查 spot_loc 的值,如果發現變動了,就要做 XMoveWindow 的
   動作。而這部分我還沒寫。希望使用 XFree86-3.9.X 的朋友可以幫忙補足。

   至於該視窗所展現出來的樣子,是一個比 xcin 主視窗還要短一些,但寬度只
   有一列的小視窗。根據定義: 該視窗只用來顯示組字狀態資料,因此,每當您
   輸入一個字鍵,該字鍵所對應的碼就會出現該視窗,而不會出現在 xcin 主視
   窗。同樣的,在組字過程中,如果目前所輸入的字鍵組剛好可以對應一些字,
   則這些字也不會出現在 xcin 主視窗,而是出現在 OverTheSpot 視窗。在做多
   重字選擇時,也是在 OverTheSpot 視窗中選擇。至於其他 xcin 的狀態資訊,
   如 (英數)(全形)(半形) 等,則還是留在 xcin 主視窗中。另外,在 bimsphone
   的自動選字模式下,所打出來的字不會馬上跑到 XIM client, 而會暫時留在
   xcin buffer 中,這些字我還是顯示在 xcin 主視窗的第一行,與原來一樣,
   而不顯示在 OverTheSpot 視窗中。原因是我覺得 OverTheSpot 視窗很小,而
   且在輸入過程中是「若隱若現」的,因此不適合用來顯示 cache 的字。

   事時上,在 OverTheSpot 模式中, xcin 主視窗是否存在已不重要了,重要的
   反而是 OverTheSpot 視窗本身,因此,一個可能的改進是在此模式下 xcin 可
   以自動變成一個 icon, 並在 icon 中顯示 (英數)(輸入法名)(全形)(半形) 等
   資訊 (我覺得這還是有必要顯示的,可以讓使用者知道目前正在中文輸入狀態)。
   但若要這樣寫的話要注意: 當游標在不同 XIM clients 間切換時, client A
   是使用 Root, client B 則使用 OverTheSpot, xcin 必須要能自動還原或變成
   icon, 這樣才能方便使用。這部分我沒去寫,我想相關的細節可以再討論。

   在進入中文輸入模式時, OverTheSpot 視窗還是不會出現,要直到您開始輸入
   第一個中文字鍵時它才會出現,但一旦您輸入好一個字後,它馬上又隱藏起來,
   等您要輸入下一個字的第一個字鍵時,它才會再出現。事實上,它只是做 Map
   與 Unmap window 的動作而已,並非 destroy 再重新 create。該 Window 的生
   命期是與 IMC 一樣的 (見後述)。


5. IC 與 IMC:

   xcin-2.5.2 與 2.5.1 最大的不同,一個是 winlist 架構 (2.5.1 無法開啟多
   重視窗),另一個就是 IC 與 IMC 分離的設計了 (2.5.1 只有 IC 而已)。而這
   兩個也是我花最多心力的地方。

   IC 是指 Input Context, 每一個 XIM client 視窗都是一個 Input Context,
   而在 xcin 這邊就會為它們分別指定一個 IC 來做服務。每個 IC 都是完全獨
   立的,這樣才能應付 clients 各 Input Contexts 獨立的運作。

   IMC 是指 Input Method Context, 它就是輸入法的實際資料結構。以前 IMC
   的部分是直接寫在 IC 裏頭的,如此當您在用滑鼠切換 client 視窗時,xcin
   視窗內容 (也就是各 IC 的輸入法目前狀態) 也跟著變。

   然而,在某些情況下,我們不希望這樣,特別是在 bimsphone 輸入法上,我
   們往往希望在切換 clients 時,輸入法的狀態不要改變,尤其是剛剛已輸入
   到 xcin buffer 裏頭的那對字。為了達到此要求,才將 IMC 部分自 IC 中分
   離。

   現在 xcin 有兩種 mode: XCIN_SINGLE_IMC ON 及 XCIN_SINGLE_IMC OFF。在
   XCIN_SINGLE_IMC OFF 時,每個 IC 都各自會有一個 IMC, 於是各 client 間
   的輸入法狀態就完全獨立。若 XCIN_SINGLE_IMC ON 時,所有的 IC 都會分享
   同一個 IMC, 於是各 client 間的輸入法狀態就完全一致。

   IC 有它的生命週期,這與 client 的 Input Context 週期是一樣的。當一個
   新的 client Input Context 產生時, xcin 這邊就產生一個新的 IC, 而當
   client 的 Input Context 結束後, xcin 這邊的 IC 也結束了。

   IMC 也有它的生命週期,但視 xcin 所處的 mode 而定。如果是 XCIN_SINGLE_IMC
   ON 時,它的生命週期就與 xcin 主程式一樣長。如果是 XCIN_SINGLE_IMC OFF
   時,它的生命週期就與 IC 一樣長。

   就 OverTheSpot 而言,我認為它應該是屬於 IMC 管理的,而非 IC, 例如在
   XCIN_SINGLE_IMC ON 下,所有的 IC 的輸入法狀態都一樣,則假設我們已開
   始在 client 視窗 A 打字, OverTheSpot window 也出現在視窗 A 的游標附
   近,如果我們臨時換到視窗 B 打字時,我希望 OverTheSpot window 可以原
   封不動地移位到視窗 B 的游標位置,而非視窗 A 那個 OverTheSpot window
   消失,視窗 B 這邊另外跑出一個 OverTheSpot window (這應該是在
   XCIN_SINGLE_IMC OFF 的情況下)。因此,我才決定將 OverTheSpot 歸於 IMC
   來管理。因此,它的生命週期要與 IMC 一樣。

   但是,我剛剛才看到,目前我的寫法是不管 xcin mode 如何, OverTheSpot
   的生命週期與 IC 都一樣,這不是我所預期的,我會將它改過來 (見 xim_IC.c
   ic_destroy(), 當 xccore->gui.winchange |= WIN_CHANGE_OVERSPOT_END 時,
   就是通知 該 IC 底下的 IMC 底下的 OverTheSpot window 要 destroy 了)。

   這裏還要提一下 IMC 的狀態: inp_state (見 include/IC.h), 這裏要很小心
   處理。它可以有如下幾個狀態:

	IM_CINPUT ON:	此 IMC 進入中文輸入模式
	IM_CINPUT OFF:  此 IMC 處於英文輸入模式
	IM_2BYTES ON:	此 IMC 進入全形輸入模式
	IM_2BYTES OFF:	此 IMC 處於半形輸入模式
	IM_XIMFOCUS ON: 此 IMC 所處 IC 進入 focus 狀態
	IM_XIMFOCUS OFF:此 IMC 所處 IC 離開 focus 狀態

   這裏討論 IM_CINPUT 與 IM_XIMFOCUS 就好,IM_2BYTES 原理與 IM_CINPUT 類
   似。所謂 IC 進入 focus 狀態,是指該 IC 所對應的 XIM client 視窗被滑鼠
   點選,成為前景視窗,接下來鍵盤打字就會直接作用在該 client 視窗上。因此:

	client 為前景視窗時但處於英文輸入時,則它的 IMC 狀態就是
		IM_CINPUT OFF, IM_XIMFOCUS ON
	client 為前景視窗時但處於中文輸入時,則它的 IMC 狀態就是
		IM_CINPUT ON, IM_XIMFOCUS ON
	client 不是前景視窗時但處於中文輸入時,則它的 IMC 狀態就是
		IM_CINPUT ON, IM_XIMFOCUS OFF

   就 OverTheSpot 視窗而言,我希望在它所屬的 IC 是在 focus 狀態,而
   且其 IMC 進入中文輸入時,它才會出現。一旦 focus 改變,或變回英文
   輸入時,它就要隱藏起來。這就是為什麼我在 gui_overspot_draw() (見
   gui_overspot.c) 中一開始時做那麼多 check 的原因。

   最後談到 spot location 的部分。前面有稍微提到, client 的游標若移
   動,則它會馬上將目前的游標位置傳給 xcin, 而 xcin 就會將它放在 spot
   location 中供 OverTheSpot 使用。 spot location 分別在 IC 與 IMC 中
   各有一份 (見 include/IC.h):

	IC->pre_attr.spot_location
	IMC->spot_loc

   事實上,我覺得 spot location 只要存在 IMC 裏頭就好了,但之所以這樣
   做的考量如下: 在 XCIN_SINGLE_IMC ON 狀態下,我本來是 focus 在 client A,
   現在改 focus 在 client B,然而 client B 在進入 focus 時並沒有通知 xcin
   它目前的游標位置 (它可能認為先前已通知過了),會發生什麼事呢?結果我猜
   是,該 OverTheSpot 可能還會留在 client A 的游標位置,甚至可能是: client
   A 雖在背景,但游標因故一直在動,而 client B 因為在前景正接受輸入中,結
   果 OverTheSpot window 就這樣在 client A B 之間快速地跳來跳去。

   因此,目前我的設計是,當 xcin 一接到 client 新的游標位置,它就存到 IC
   的 spot location 中,若目前此 IC 正處於 focus 狀態時,它再拷備一份給目
   前它所使用的 IMC 的 spot location 去, 而 OverTheSpot window 永遠只參考 
   IMC 的 spot location 來移動視窗。如此,各 IC 可以隨時追蹤其 client 的
   游標位置,而 OverTheSpot window 中位隨著目前 focus 中的 client 的游標
   移動。

   啊! 又被我見到一個 bug, 我目前的程式沒有這樣寫,當 IC 接到一個新的 spot
   location 時,馬上會拷一份給 IMC, 結果就會造成上頭一樣的問題。我想 IC 這
   邊還需要增加一個 flag, 以標示目前它是否處於 focus 狀態才行 (目前沒有),
   我晚一點就會改過來。


6. 結語:

   我在寫 OverTheSpot 部分時,是在 Root input style 底下做的,由於 spot
   location 永遠都固定,所以 OverTheSpot window 自然不會隨著游標跑,也難
   以鑑別各 client 間 focus 切換對 OverTheSpot window 的影響,所以等於是
   瞎子模象在寫程式。我希望使用 XFree86-3.9.X 的朋友可以幫忙完成我未完成
   的部分,並幫忙測試。

   同時,我也歡迎高手們幫忙追查 XFree86-3.3.X 無法使用 xcin OverTheSpot
   的真正原因,必竟目前絕大部分的 GNU/Linux 與 FreeBSD 系統都還在跑 
   XFree86-3.3.X, 如果它不 work, 我們現在做這 OverTheSpot 也是枉然的。
   我很希望大家都能輕易使用它,享受它所帶來的便利。


T.H.Hsieh

----- End forwarded message -----


Reply to: