X11: 入力メソッド (1) - XIM

入力メソッド
日本語などを入力するためには、入力メソッドによる操作が必要になります。

X において入力メソッドを使用するためには、まず最初にロケールを設定した後、XOpenIM() で入力メソッドを開きます。
この関数で返される XIM (XID) は、1つのアプリケーションに対して1つ必要になります。
入力メソッドを開く/閉じる
XIM XOpenIM(Display *display, XrmDatabase db, char *res_name, char *res_class);

Status XCloseIM(XIM im);

現在のロケールと、X ロケール修飾子で指定されている入力メソッドを開きます。

db入力メソッドによって使用されるリソースデータベース。
NULL でなし。
res_name
res_class
アプリケーションのリソース名とクラス名。
この入力メソッドで使用されるコンテキストにおいて、共通のリソースを検索するために使用される。
使用しないのであれば、NULL でよい。
戻り値失敗時、NULL が返ります。
コールバック関数
デスクトップの起動時にクライアントのアプリケーションを起動する場合、入力メソッドのサーバーが起動する前に、アプリケーションが起動されてしまう場合があります。

その場合は、入力メソッドが開けないので、代わりに、入力メソッドが使用可能な状態になった時に呼ばれるコールバック関数を登録できます。

Bool XRegisterIMInstantiateCallback(Display *display, XrmDatabase db,
    char *res_name, char *res_class, XIDProc callback, XPointer *client_data);

Bool XUnregisterIMInstantiateCallback(Display *display, XrmDatabase db,
    char *res_name, char *res_class, XIDProc callback, XPointer *client_data);

typedef void (*XIDProc)(Display *display, XPointer client_data, XPointer call_data);

XRegisterIMInstantiateCallback でコールバック関数を登録し、XUnregisterIMInstantiateCallback でコールバック関数を削除します。

XOpenIM() で NULL が返った場合、コールバック関数を登録し、コールバック関数が来たら XOpenIM() で開いて、コールバック関数を削除します。
入力スタイル
入力メソッドを開いたら、その入力メソッドがサポートしている入力スタイルを取得する必要があります。

入力メソッドは、入力中の情報として、「ステータス領域」「前編集領域」「補助領域」を表示する場合があります。

前編集は、変換前のテキストが表示される領域です。Xlib では、以下のスタイルがサポートされています。

on-the-spotアプリケーションが自身の領域で、変換前のテキストを表示します
over-the-spot挿入ポイントの上に前編集ウィンドウを表示
off-the-spotアプリケーションウィンドウの下部など、位置を指定せずに前編集ウィンドウを表示
ルートウィンドウルートウィンドウの子として前編集ウィンドウを表示

入力メソッドによって、サポートしているスタイルが異なります。
現在では主に「on-the-spot」か「off-the-spot」が使われます。
IM の値を取得
char *XGetIMValues(XIM im,...);

指定した入力メソッドが設定している値を読み込みます。

可変引数には、「値の名前の文字列のポインタ」と「値を取得する変数のポインタ」の2つをペアにして渡し、NULL ポインタで終了します。
※終了時の NULL は、数値の 0 として渡さず、明確に (void *)0 としてください。

成功した場合は NULL を返し、それ以外の場合は、取得できなかった最初の名前を返します。

値の名前
名前の文字列は、以下のマクロ名で定義されています。

XNQueryInputStyleサポートしている入力スタイルのリスト (XIMStyles **)。
使用後は XFree() で解放する。
XNQueryIMValuesListサポートされている XIM のプロパティ名のリスト (XIMValuesList **)。
XFree() で解放する。
XNQueryICValuesListサポートされている XIC のプロパティ名のリスト (XIMValuesList **)。
XFree() で解放する。
XNQueryInputStyle
入力スタイルのリストは、XNQueryInputStyle の名前で取得します。

確保された XIMStyles 構造体のポインタが返るので、値は (XIMStyles **) で渡します。
使用後は、XIMStyles のポインタを XFree() で解放します。

typedef unsigned long XIMStyle;

typedef struct {
  unsigned short count_styles; //配列個数
  XIMStyle *supported_styles;  //スタイル値の配列
} XIMStyles;

//前編集のフラグ
#define XIMPreeditArea        0x0001L
#define XIMPreeditCallbacks   0x0002L
#define XIMPreeditPosition    0x0004L
#define XIMPreeditNothing     0x0008L
#define XIMPreeditNone        0x0010L

//ステータスのフラグ
#define XIMStatusArea         0x0100L
#define XIMStatusCallbacks    0x0200L
#define XIMStatusNothing      0x0400L
#define XIMStatusNone         0x0800L

スタイル値は、前編集とステータス領域に関するフラグが指定されています。

XIMPreeditArea以下の領域値が必要。
XNArea, XNAreaNeeded
XIMPreeditPosition以下の位置の値が必要。
XNSpotLocation, XNFocusWindow
XIMPreeditCallbacksコールバック関数が必要。
XNPreeditStartCallback、XNPreeditDoneCallback、XNPreeditDrawCallback、XNPreeditCaretCallback
XIMPreeditNothing前編集に関する値は必要ない。
XIMPreeditNone入力メソッドは、前編集を提供しません。

XIMPreeditCallbacks の場合は、on-the-spot スタイル。
XIMPreeditNothing の場合は、off-the-spot スタイルとなります。
IM 破棄時のコールバック
入力メソッドが何らかの理由でサービスを停止した場合、IM にコールバック関数を設定することで、それを検知することができます。

char *XSetIMValues(XIM im,...);

名前に XNDestroyCallback マクロ、値に XIMCallback 構造体のポインタを指定します。

typedef struct {
  XPointer client_data;
  XIMProc callback;
} XIMCallback;

typedef void (*XIMProc)(XIM im, XPointer client_data, XPointer call_data);
//call_data は常に NULL

このコールバックが呼び出された後は、入力メソッドが閉じられ、関連する入力コンテキストも Xlib によって破棄されます。
よって、クライアントは、この関数内で XCloseIM() や XDestroyIC() を呼ばないこと。
プログラム
入力メソッドを開いて、プロパティ値を表示します。

$ cc -o run 22-im1.c util.c -lX11

<22-im1.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include "util.h"

static void _put_style(XIMStyle n)
{
    if(n & XIMPreeditArea) printf(" PreeditArea");
    if(n & XIMPreeditCallbacks) printf(" PreeditCallbacks");
    if(n & XIMPreeditPosition) printf(" PreeditPosition");
    if(n & XIMPreeditNothing) printf(" PreeditNothing");
    if(n & XIMPreeditNone) printf(" PreeditNone");

    if(n & XIMStatusArea) printf(" StatusArea");
    if(n & XIMStatusCallbacks) printf(" StatusCallbacks");
    if(n & XIMStatusNothing) printf(" StatusNothing");
    if(n & XIMStatusNone) printf(" StatusNone");

    printf("\n");
}

int main(int argc,char **argv)
{
    Display *disp;
    XIM im;
    XIMStyles *styles = NULL;
    XIMValuesList *imval = NULL, *icval = NULL;
    int i;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    init_locale();

    //IM 開く

    im = XOpenIM(disp, NULL, NULL, NULL);
    if(!im)
    {
        printf("failed XOpenIM\n");
        XCloseDisplay(disp);
        return 0;
    }

    //IM プロパティの取得

    XGetIMValues(im,
        XNQueryInputStyle, &styles,
        XNQueryIMValuesList, &imval,
        XNQueryICValuesList, &icval,
        (void *)0);

    //入力スタイル

    if(styles)
    {
        printf("-- input style --\n");
        
        for(i = 0; i < styles->count_styles; i++)
        {
            printf("[%d]", i);

            _put_style(styles->supported_styles[i]);
        }

        XFree(styles);
    }

    //IM 値の名前

    if(imval)
    {
        printf("\n-- IM values --\n");

        for(i = 0; i < imval->count_values; i++)
            printf("[%d] %s\n", i, imval->supported_values[i]);

        XFree(imval);
    }

    //IC 値の名前

    if(icval)
    {
        printf("\n-- IC values --\n");

        for(i = 0; i < icval->count_values; i++)
            printf("[%d] %s\n", i, icval->supported_values[i]);

        XFree(icval);
    }

    //

    XCloseIM(im);

    XCloseDisplay(disp);

    return 0;
}
結果 (fcitx5 の場合)
※fcitx の場合、XIMPreeditCallbacks が使用できるようにするには、「アドオン」→「X Input Method フロントエンド」で、「XIM で On The Spot スタイルを使う」のチェックが ON になっている必要があります。

-- input style --
[0] PreeditPosition StatusNothing
[1] PreeditCallbacks StatusNothing
[2] PreeditNothing StatusNothing
[3] PreeditPosition StatusCallbacks
[4] PreeditCallbacks StatusCallbacks
[5] PreeditNothing StatusCallbacks

-- IM values --
[0] queryInputStyle

-- IC values --
[0] inputStyle
[1] clientWindow
[2] focusWindow
[3] filterEvents
[4] preeditAttributes
[5] statusAttributes
[6] fontSet
[7] area
[8] areaNeeded
[9] colorMap
[10] stdColorMap
[11] foreground
[12] background
[13] backgroundPixmap
[14] spotLocation
[15] lineSpace
[16] separatorofNestedList

IC は、入力コンテキストを意味します。

入力メソッドによって、表示される値は異なります。
On The Spot が使用出来ない場合
fcitx で、「XIM で On The Spot スタイルを使う」が OFF の場合は、以下のようになります。

[0] PreeditPosition StatusArea
[1] PreeditPosition StatusNothing
[2] PreeditPosition StatusNone
[3] PreeditNothing StatusNothing
[4] PreeditNothing StatusNone