Help us understand the problem. What is going on with this article?

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サンプルを作成しました。

ai56go
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away