text-input v3 : 入力メソッドからのテキスト入力

text-input
unstable の「text-input-unstable-v3.xml」のプロトコルを解説します。

これは、日本語入力など、入力メソッドからのテキストを受け付けるためのものです。

入力メソッドの管理はサーバー側が行い、サーバーは入力メソッドから受け取ったデータを元に、各クライアントへデータを送る仕組みとなっています。
実行環境
2019年12月時点では、GNOME が対応しています。
KDE では実装されていませんでした。
GNOME での実行環境
パッケージ
まず、以下のパッケージが必要なので、インストールされていない場合は、インストールします。

gnome-control-centerGNOME の設定パネル。
入力メソッドの設定を行うのに必要。
ibusibus の入力メソッドフレームワーク。
GNOME は ibus に対応している。
ibus-anthy
ibus-kkc
ibus の日本語入力。
好きなものを一つ使ってください。

GNOME での設定
※ GNOME 上で上記パッケージを新規インストールした場合は、一度ログアウトして、再ログインしてください。

GNOME の設定パネルを開いて、「地域と言語」を選択します。

「入力ソース」のリストの部分で「+」を押して、「日本語」を選択。
その中の「日本語 (Anthy)」または「日本語 (かな漢字)」を選択して、「追加」ボタンを押します。

リスト内に、選択したものが追加されているので、それをドラッグして先頭に持ってきます。
これで設定は終了です。

ステータスバーのアイコンから選択すれば、入力メソッドを切り替えられます。
場合によっては、入力モードも選択する必要があります。

切り替え
GTK+ などのアプリケーション上では、「半角/全角」キーで、直接入力/日本語入力を切り替えられますが、ツールキットを使わずに独自で作られたクライアントの場合、「半角/全角」キーでは切り替えられません。

入力が切り替わらない場合は、「Super (Windows キー) + Space」で切り替えるか、ステータスバーのアイコンから選択してください。

なお、入力メソッドの切り替えだけでなく、入力モードの切り替えも必要な場合があります。
プログラム
$ cc -o test p02_textinput.c client.c imagebuf.c \
 text-input-unstable-v3-protocol.c -lwayland-client -lrt

scanner で、「text-input-unstable-v3-client-protocol.h」「text-input-unstable-v3-protocol.c」を生成してください。

p02_textinput.c
/******************************
 * text-input-unstable-v3
 ******************************/

#include <stdio.h>
#include <string.h>
#include <linux/input.h>

#include <wayland-client.h>
#include "text-input-unstable-v3-client-protocol.h"

#include "client.h"
#include "imagebuf.h"


//-------------

struct zwp_text_input_manager_v3 *g_input_manager = NULL;
struct zwp_text_input_v3 *g_text_input;

#define INPUTBOX_X 10
#define INPUTBOX_Y 10
#define INPUTBOX_W 200
#define INPUTBOX_H 60

//-------------


//-----------------------
// zwp_text_input_v3
//-----------------------


/* enter */

static void _input_enter(void *data, struct zwp_text_input_v3 *text_input,
    struct wl_surface *surface)
{
    printf("text_input # enter\n");

    zwp_text_input_v3_enable(text_input);

    zwp_text_input_v3_set_cursor_rectangle(text_input,
        INPUTBOX_X, INPUTBOX_Y, INPUTBOX_W, INPUTBOX_H);

    zwp_text_input_v3_commit(text_input);
}

/* leave */

static void _input_leave(void *data, struct zwp_text_input_v3 *text_input,
    struct wl_surface *surface)
{
    printf("text_input # leave\n");

    zwp_text_input_v3_disable(text_input);
    zwp_text_input_v3_commit(text_input);
}

/* preedit_string */

static void _input_preedit_string(void *data, struct zwp_text_input_v3 *text_input,
    const char *text, int32_t cursor_begin, int32_t cursor_end)
{
    printf("text_input # preedit_string | text:\"%s\", cursor_begin:%d, cursor_end:%d\n",
        text, cursor_begin, cursor_end);
}

/* commit_string */

static void _input_commit_string(void *data, struct zwp_text_input_v3 *text_input,
    const char *text)
{
    printf("text_input # commit_string | text:\"%s\"\n", text);
}

/* delete_surrounding_text */

static void _input_delete_surrounding_text(void *data,
    struct zwp_text_input_v3 *text_input,
    uint32_t before_length, uint32_t after_length)
{
    printf("text_input # delete_surrounding_text | before_length:%u, after_length:%u\n",
        before_length, after_length);
}

/* done */

static void _input_done(void *data, struct zwp_text_input_v3 *text_input,
    uint32_t serial)
{
    printf("text_input # done | serial:%u\n", serial);

#if 0
    //周囲のテキストセット
    
    zwp_text_input_v3_set_surrounding_text(text_input,
        "[text]", 1, 5);

    zwp_text_input_v3_set_text_change_cause(text_input,
        ZWP_TEXT_INPUT_V3_CHANGE_CAUSE_INPUT_METHOD);

    zwp_text_input_v3_commit(text_input);
#endif
}


static const struct zwp_text_input_v3_listener g_text_input_listener = {
    _input_enter, _input_leave, _input_preedit_string,
    _input_commit_string, _input_delete_surrounding_text, _input_done
};


//-----------------------
// wl_keyboard
//-----------------------


static void _keyboard_keymap(void *data, struct wl_keyboard *keyboard,
    uint32_t format, int32_t fd, uint32_t size)
{
}

static void _keyboard_enter(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, struct wl_surface *surface, struct wl_array *keys)
{
    printf("wl_keyboard # enter\n");
}

static void _keyboard_leave(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, struct wl_surface *surface)
{
    printf("wl_keyboard # leave\n");
}

/* キー */

static void _keyboard_key(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
    Client *p = (Client *)data;

    printf("wl_keyboard # key | %s, key:%u\n",
        (state == WL_KEYBOARD_KEY_STATE_PRESSED)? "press":"release", key);

    if(state != WL_KEYBOARD_KEY_STATE_PRESSED) return;

    switch(key)
    {
        //ESC キーで終了
        case KEY_ESC:
            p->finish_loop = 1;
            break;
    }
}

static void _keyboard_modifiers(void *data, struct wl_keyboard *keyboard,
    uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group)
{

}

static void _keyboard_repeat_info(void *data, struct wl_keyboard *keyboard,
    int32_t rate, int32_t delay)
{

}

static const struct wl_keyboard_listener g_keyboard_listener = {
    _keyboard_keymap, _keyboard_enter, _keyboard_leave, _keyboard_key,
    _keyboard_modifiers, _keyboard_repeat_info
};


//------------------------


static void _registry_global(
    void *data,struct wl_registry *reg,uint32_t id,const char *name,uint32_t ver)
{
    if(strcmp(name, "zwp_text_input_manager_v3") == 0)
    {
        g_input_manager = wl_registry_bind(reg, id,
            &zwp_text_input_manager_v3_interface, 1);
    }
}


//---------------


int main(void)
{
    Client *p;
    Window *win;

    p = Client_new(0);

    p->init_flags = INIT_FLAGS_SEAT | INIT_FLAGS_KEYBOARD;
    p->keyboard_listener = &g_keyboard_listener;
    p->registry_global = _registry_global;
    
    Client_init(p);

    if(!g_input_manager)
    {
        printf("[!] not found 'zwp_text_input_manager_v3'\n");
        Client_destroy(p);
        return 1;
    }

    //zwp_text_input

    g_text_input = zwp_text_input_manager_v3_get_text_input(
        g_input_manager, p->seat);

    zwp_text_input_v3_add_listener(g_text_input,
        &g_text_input_listener, p);

    //ウィンドウ

    win = Window_create(p, 256, 256, NULL);

    ImageBuf_fill(win->img, 0xffff0000);

    ImageBuf_box(win->img,
        INPUTBOX_X, INPUTBOX_Y, INPUTBOX_W, INPUTBOX_H,
        0xff000000);
    
    Window_update(win);

    //

    Client_loop_simple(p);

    //解放

    Window_destroy(win);

    zwp_text_input_v3_destroy(g_text_input);
    zwp_text_input_manager_v3_destroy(g_input_manager);

    Client_destroy(p);

    return 0;
}

操作方法
ESC キーで終了します。

入力メソッドが有効な状態であれば、そのままテキストを入力できます。
(ウィンドウ上にテキストの表示は行いませんので、イベントの出力だけ確認してください)
zwp_text_input_manager_v3
まず、zwp_text_input_manager_v3 をバインドします。

zwp_text_input_manager_v3 と wl_seat が作成できたら、zwp_text_input_v3 を作成し、ハンドラを設定します。

## zwp_text_input_manager_v3 破棄

void zwp_text_input_manager_v3_destroy(
    struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3);

## zwp_text_input_v3 作成

struct zwp_text_input_v3 *zwp_text_input_manager_v3_get_text_input(
    struct zwp_text_input_manager_v3 *zwp_text_input_manager_v3,
    struct wl_seat *seat);

## ハンドラ設定

int zwp_text_input_v3_add_listener(
    struct zwp_text_input_v3 *zwp_text_input_v3,
    const struct zwp_text_input_v3_listener *listener, void *data);

## zwp_text_input_v3 破棄

void zwp_text_input_v3_destroy(
    struct zwp_text_input_v3 *zwp_text_input_v3);
zwp_text_input_v3 のイベント
void (*enter)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
    struct wl_surface *surface);

void (*leave)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
    struct wl_surface *surface);

void (*preedit_string)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
    const char *text, int32_t cursor_begin, int32_t cursor_end);

void (*commit_string)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
    const char *text);

void (*delete_surrounding_text)(void *data,
    struct zwp_text_input_v3 *zwp_text_input_v3,
    uint32_t before_length, uint32_t after_length);

void (*done)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
    uint32_t serial);
enter
指定サーフェスにテキスト入力フォーカスが来た時。

入力メソッドからの入力を有効にするなら、ここで zwp_text_input_v3_enable() を実行します。
leave
指定サーフェスから入力フォーカスが離れた時。

zwp_text_input_v3_disable() で、テキスト入力を無効にします。
preedit_string
新しい編集前テキストが来た時。

ここでは、前回取得した編集前テキストを削除して、置き換えます。

クライアントで実際にテキストの表示処理などを行うのは done イベントが来た時なので、送られてきた情報を一旦保存しておきます。

text は UTF-8 文字列。

cursor_begin, cursor_end は、カーソルの先頭と終端の位置。
text の先頭からのバイト数です。
両方が -1 なら、カーソルは非表示にします。
両方が同じ値ならカーソルは線で表示し、異なる値であれば、間の文字列は強調表示します。
commit_string
確定した文字列が来ます。
delete_surrounding_text
周囲のテキストを削除する必要がある時に来ます。

before_length, after_length は、現在のカーソル位置からの前後のバイト数。
done
preedit_string, commit_string, delete_surrounding_text イベントの変更を確定させる時に来ます。

クライアントは、この done イベントが来た時に、送られてきた情報を元にテキストを操作したり、表示したりします。

引数の serial は、このオブジェクトに対する zwp_text_input_v3_commit() の総実行回数と等しくなければなりません。
処理
各操作については、関数リファレンスの方をご覧ください。

zwp_text_input_v3 の状態変更については、すべて zwp_text_input_v3_commit() で変更を適用させる必要があります。
enter と leave
static void _input_enter(void *data, struct zwp_text_input_v3 *text_input,
    struct wl_surface *surface)
{
    printf("text_input # enter\n");

    zwp_text_input_v3_enable(text_input);

    zwp_text_input_v3_set_cursor_rectangle(text_input,
        INPUTBOX_X, INPUTBOX_Y, INPUTBOX_W, INPUTBOX_H);

    zwp_text_input_v3_commit(text_input);
}

static void _input_leave(void *data, struct zwp_text_input_v3 *text_input,
    struct wl_surface *surface)
{
    printf("text_input # leave\n");

    zwp_text_input_v3_disable(text_input);
    zwp_text_input_v3_commit(text_input);
}

zwp_text_input_v3_set_cursor_rectangle() で、エディタ部分の範囲を指定しています。

この範囲に重ならない位置に、変換候補ウィンドウが表示されます。

範囲が設定されていない場合は、適当な位置に表示されます。

GNOME の場合は、範囲の左右中央、下端の位置に表示されました。
イベント
text_input # preedit_string | text:"k", cursor_begin:1, cursor_end:1
text_input # done | serial:3
wl_keyboard # key | release, key:37
text_input # preedit_string | text:"か", cursor_begin:1, cursor_end:1
text_input # done | serial:4
wl_keyboard # key | release, key:30
text_input # preedit_string | text:"かn", cursor_begin:2, cursor_end:2
text_input # done | serial:5
wl_keyboard # key | release, key:49
text_input # preedit_string | text:"かん", cursor_begin:2, cursor_end:2
text_input # done | serial:6
wl_keyboard # key | release, key:49
text_input # preedit_string | text:"かんj", cursor_begin:3, cursor_end:3
text_input # done | serial:7
text_input # preedit_string | text:"かんじ", cursor_begin:3, cursor_end:3
text_input # done | serial:8
wl_keyboard # key | release, key:36
wl_keyboard # key | release, key:23
▼ 変換
text_input # preedit_string | text:"漢字", cursor_begin:0, cursor_end:0
text_input # done | serial:9
wl_keyboard # key | release, key:57
▼ 変換候補から選択
text_input # preedit_string | text:"感じ", cursor_begin:0, cursor_end:0
text_input # done | serial:10
wl_keyboard # key | release, key:57
text_input # preedit_string | text:"感じ", cursor_begin:0, cursor_end:0
text_input # done | serial:11
▼ 確定
text_input # preedit_string | text:"(null)", cursor_begin:0, cursor_end:0
text_input # commit_string | text:"感じ"
text_input # preedit_string | text:"(null)", cursor_begin:0, cursor_end:0
text_input # done | serial:25

入力メソッドが有効な間は、入力メソッド側で処理されたキー押しイベントは発生しませんが、キー離しイベントは発生しています。

ここでは、入力ごとに編集前テキストが変化し、カーソルは終端位置に移動しています。

※ プロトコルの説明では、カーソル位置は「バイト単位」となっていましたが、GNOME では「文字単位」になっているようです (2019年12月時点)

変換が確定されたら、commit_string イベントが来るので、done イベント時に、確定テキストを処理します。

確定後は、編集前テキストはない状態になります。
surrounding_text
GNOME 上では、surrounding_text (周囲のテキスト) の処理が確認できなかったので、ここでは省略します。