C
Linux
X11
日本語入力
input-method

XIM(X Input Method)日本語入力の流れ

More than 1 year has passed since last update.

2018年3月 作成

「X Window Systemの日本語入力はどうなっているんだろう?」

そう考えた筆者が、日本語入力の流れを調べてみました。

 C言語、X11プログラミングに関する文章です。

 使用OS:ubuntu16.04


日本語入力に対応していないプログラム

まず初めにキーボードから文字を入力するプログラムを作ります。


example01.c

#include <stdio.h>

#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int main(void)
{
Display* dpy = XOpenDisplay(NULL);

Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 100, 100, 0, 0, 0);
XMapRaised(dpy, win);
XSync(dpy, False);

XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask);

XEvent ev = {};
while(1){
XNextEvent(dpy, &ev);
switch(ev.type){
case KeyPress: {
KeySym keysym = NoSymbol;
char text[1024] = {};

XLookupString((XKeyEvent *)&ev, text, sizeof(text) - 1, &keysym, NULL);
printf("Got chars: (%s)\n", text);

if(keysym == XK_Escape){
puts("Exiting because escape was pressed.");
return 0;
}

}
break;
}
}

return 0;
}


コンパイルパラメータは以下の通りです。

gcc -o example01 example01.c -lX11

コンパイルして端末エミュレータ(GNOME端末など)から実行すると、ウインドウに入力した文字が端末エミュレータにprintfで表示される単純なものです。


日本語入力に対応したプログラム

上記のプログラムを改良して、日本語入力ができるようにしたのが、このプログラムです。


example02.c

#include <stdio.h>

#include <locale.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

int main(void){

#if 1
setlocale(LC_ALL, "");
#endif

Display* dpy = XOpenDisplay(NULL);

Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 100, 100, 0, 0, 0);
XMapRaised(dpy, win);
XSync(dpy, False);

#if 1
XSetLocaleModifiers("");

XIM xim = XOpenIM(dpy, 0, 0, 0);
if(!xim){
// fallback to internal input method
XSetLocaleModifiers("@im=none");
xim = XOpenIM(dpy, 0, 0, 0);
}

XIC xic = XCreateIC(xim,
XNInputStyle, XIMPreeditNothing | XIMStatusNothing,
XNClientWindow, win,
XNFocusWindow, win,
NULL);
XSetICFocus(xic);
#endif

XSelectInput(dpy, win, KeyPressMask | KeyReleaseMask);

XEvent ev = {};
while(1){
XNextEvent(dpy, &ev);

#if 1
if(XFilterEvent(&ev, None) == True) continue;
#endif

switch(ev.type){
case KeyPress: {
KeySym keysym = NoSymbol;
char text[1024] = {};

#if 1
Xutf8LookupString(xic, &ev.xkey, text, sizeof(text) - 1, &keysym, NULL);
printf("Got chars: (%s)\n", text);
#else
XLookupString((XKeyEvent *)&ev, text, sizeof(text) - 1, &keysym, NULL);
printf("Got chars: (%s)\n", text);
#endif

// example of responding to a key
if(keysym == XK_Escape){
puts("Exiting because escape was pressed.");
return 0;
}
}
break;
}
}

return 0;

}


コンパイルパラメータは以下の通りです。

gcc -o example02 example02.c -lX11

X11プログラム(例えば、xterm)で日本語が入力できるのあれば、このプログラムでも日本語が入力できます。

(このプログラムの参考URL https://gist.github.com/baines/5a49f1334281b2685af5dcae81a6fa8a)

数個の関数をプログラムに追加しましたが、IMサーバに直接関わる関数は、次の四つです。

  XIM xim = XOpenIM()   //XIMプロトコルでIMサーバと接続

XIC xic = XCreateIC() //IMサーバとのコンテキスト作成
XSetICFocus() //フォーカスを設定
XFilterEvent() //IMサーバへイベント送信

そして、IMサーバとのキーイベントのやり取りは、次の二つで行います。

  XNextEvent()        //IMサーバからのイベントを受け取る

XFilterEvent() //IMサーバにイベントを送る

(IMサーバとは、fcitxやibusなどを指しています)


日本語入力の大まかな流れ

図.gif

図のように、X11プログラムはキーイベントをXNextEvent関数で受け取り、XFilterEvent関数でIMサーバにそのイベントを送るかどうか判断します。

XFilterEvent関数はIMサーバにそのイベントを送る場合、戻り値をTrueで返します。

X11プログラムはIMサーバからのキーイベントもXNextEvent関数を使って受け取ります。このIMサーバからのイベントをXFilterEvent関数に渡しても戻り値がFalseで返り、IMサーバに再度送られることはありません。

(IMサーバとIME間のやり取りは、この文章では触れません)


IMサーバ

もう少し深入りして、IMサーバについて調べてみます。

IMサーバは、IMdkitという機能を使って実装します。下記URLにプログラムコードを見つけましたが、メンテナンスされておらずコンパイルエラーが出ます。

https://www.x.org/releases/unsupported/lib/IMdkit/

さらに探してみると、ibusやfcitxでは独自の修正を加えてIMdkitの機能を使っていることが分かり、下記URLからIMdkitのソースをダウンロードすることができました。

https://github.com/fcitx/fcitx/tree/master/src/frontend/xim/lib

IMサーバのサンプルプログラムは下記URLからダウンロードしました。

https://www.x.org/releases/unsupported/lib/IMdkit/Xi18n_sample/


sampleIM(IMサーバ)のコンパイル

ダウンロードしたプログラムコードのままではコンパイルが通らないため、若干の修正を加えたIMdkitとサンプルIMサーバを下記URLに準備しました。

http://ai56go.ivory.ne.jp/sample/sampleIM.zip

このzipファイルを展開すると、buildディレクトリがあります。

このbuildディレクトリ内に移り、以下のコマンドを打つと、sampleIMプログラムが作られます。

chmod 755 build.sh

chmod 755 build2.sh
./build.sh
./build2.sh

(sampleIM.cのソースコードが古いため、多くのwarningメッセージが出ますが、sampleIMプログラムは作成されます)


sampleIM(IMサーバ)の実行

export XMODIFIERS=@im=sampleIM

./sampleIM &
xterm

と順番に打ってxtermを起動します。

(ヒント、xtermがない場合、代わりに上記で紹介したexample02を使って下さい。GNOME端末などはXIM以外の方法でIMサーバと通信をするのでここでは使えません)

xtermでshift+spaceと打ってからキーボードから文字を打つと、

sampleIMを起動した端末エミュレータに入力した文字が表示されます。

実際のIMサーバの場合、このタイミングでIMEと通信をして日本語変換の処理をします。

ctrl+kと打つと"これは IM からの確定文字列です。"とxtermに表示されます。

最後にshift+spaceを打つことで、普通にxtermに文字が入力できます。

このsampleIMは

 shift+space 入力の切り替え

 ctrl+k 確定

の機能がありますが、日本語変換は行わないので、確定で返される文字列はいつも同じものになります。

sampleIMを終われせるには、下記のように、psでプロセスIDを調べてkillすることで終わらせます。

ps | grep sampleIM

kill (プロセスID)


sampleIMの解説

このsampleIMは、IMサーバとしての機能が最低限備わっています。日本語変換はIMEの仕事なのでこの文章では触れません。

sampleIMの動作をプログラムの視点から見ていくと、

まず、sampleIM.cの398行に

    ims = IMOpenIM(dpy,

とあり、IMサーバの初期化をしています。

このIMOpenIM関数の引数として、401行

    IMServerName, imname,

とあります。このimname値は"sampleIM"となっていて、

上記で入力したexport XMODIFIERS=@im=sampleIMの値と一致していないといけません。

次に、425行に

    IMProtocolHandler, MyProtoHandler,

とあり、コールバック関数を指定します(ここではMyProtoHandler関数)。

次に、440行の

    XNextEvent(dpy, &event);

で、X11プログラム(xtermなど)からのキーイベントを受け取り、

X11プログラムからのイベントであった場合のみ、441行

    if (XFilterEvent(&event, None) == True)

      continue;


のXFilterEvent関数はMyProtoHandler関数を呼び、戻り値にTrueを戻し、continue;する仕組みになっています。

249行の

    MyProtoHandler(ims, call_data)

でIMサーバとしての処理を行います。

もし、sampleIMをもっと充実させるのであれは、このMyProtoHandler関数を充実させることになるでしょう。

IMdkitのAPIは下記URLに記載されています。

https://www.x.org/releases/unsupported/lib/IMdkit/doc/API.text

IMdkitで必要とされる関数は下記のとおりで多くはありません。

XIMS IMOpenIM(Display display,...)

char *IMSetIMValues(XIMS ims,...)
char *IMGetIMValues(XIMS ims,...)
void IMCloseIM(XIMS ims)
int IMPreeditStart(XIMS ims, XPointer im_protocol)
int IMPreeditEnd(XIMS ims, XPointer im_protocol)
void IMForwardEvent(XIMS ims, XPointer im_protocol)
void IMCommitString(XIMS ims, XPointer im_protocol)
int IMCallCallback(XIMS ims, XPointer im_protocol)


最後に

この文章は、XIMを使った日本語入力の流れについて説明しました。

・日本語入力を行うための関数(サンプルプログラム)を紹介。

・イベントのやり取りには、XNextEvent関数、XFilterEvent関数を使う。

・IMサーバはIMdkit機能を使う。

また、オリジナルのIMdkitサンプルではコンパイルエラーが出る状態ですが、エラーがでないようにまとめたIMdkitサンプルを作成しました。