Wayland: 入力メソッドからのテキスト入力

text-input
日本語入力をする場合など、入力メソッドからのテキストを簡単に受け取りたい場合は、text-input を使います。

2025年2月時点では、まだ stable になっていないので、unstable の「text-input-unstable-v3.xml」を使用します。

入力メソッドの管理はサーバー側が行い、サーバーは、入力メソッドから受け取ったデータを元に、各クライアントへデータを送る仕組みとなっています。
GNOME での実行環境
GNOME で日本語入力をする場合、以下のパッケージが必要です。

ibusibus の入力メソッドフレームワーク。
GNOME は ibus に対応している。
ibus-anthy
ibus-kkc
ibus の日本語入力。
好きなものを一つ使ってください。
プログラム
wayland-scanner で、unstable/text-input/text-input-unstable-v3.xml から
「text-input-unstable-v3-client-protocol.h」「text-input-unstable-v3-protocol.c」を生成してください。

$ wayland-scanner client-header text-input-unstable-v3.xml text-input-unstable-v3-client-protocol.h
$ wayland-scanner private-code text-input-unstable-v3.xml text-input-unstable-v3-protocol.c

$ cc -o test 18-textinput.c client.c imagebuf.c \
 xdg-shell-protocol.c text-input-unstable-v3-protocol.c -lwayland-client -lrt

ESC キーで終了します。

全角/半角などのキーで入力メソッドを切り替えて、入力してください。
ウィンドウ上にテキストの表示は行わないため、イベントの出力で確認してください。

<18-textinput.c>
#include <stdio.h>
#include <string.h>
#include <linux/input-event-codes.h>

#include <wayland-client.h>
#include "xdg-shell-client-protocol.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);
}


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
};


//======================


static void _key_press(client *p,uint32_t key,uint32_t serial)
{
    printf("key_press: %u\n", key);

    //ESC キーで終了
    if(key == KEY_ESC)
        p->finish_loop = 1;
}

static void _registry_global(
    client *cl,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);
    }
}

/* main */

int main(void)
{
    client *p;
    Toplevel *win;

    p = client_new(0);

    p->init_flags = CLIENT_INIT_FLAGS_SEAT | CLIENT_INIT_FLAGS_KEYBOARD;
    p->registry_global = _registry_global;
    p->func_keyboard_key_press = _key_press;
    
    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 = toplevel_create(p, 256, 256, NULL);

    imagebuf_fill(win->sf.img, 0xffff0000);

    imagebuf_box(win->sf.img,
        INPUTBOX_X, INPUTBOX_Y, INPUTBOX_W, INPUTBOX_H, 0xff000000);
    
    //イベントループ

    client_loop_simple(p);

    //解放

    toplevel_destroy(win);

    zwp_text_input_v3_destroy(g_text_input);
    zwp_text_input_manager_v3_destroy(g_input_manager);

    client_destroy(p);

    return 0;
}
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);

//zwp_text_input_v3 ハンドラ設定

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);

struct zwp_text_input_v3_listener {
    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);
};

//zwp_text_input_v3 破棄

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

サーフェスにテキスト入力フォーカスが来た時。

このウィンドウ上で、入力メソッドからの入力を有効にしたい場合、ここで zwp_text_input_v3_enable() を実行します。
leave
void (*leave)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, struct wl_surface *surface);

enter 状態のサーフェスから、入力フォーカスが離れた時。

zwp_text_input_v3_disable() で、テキスト入力を無効にします。
preedit_string
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);

新しい編集前テキストが来た時。

編集前 (preedit) テキストとは、現在入力中の、確定前のテキストです。
「あか (AKA)」と入力して、スペースキーで「赤」に変換した場合、「あ」→「あk」「あか」→「赤」の順で来ます。

編集前テキストを自身のウィンドウ上に表示したいクライアントは、前回のイベントで取得した文字列を破棄して、新しいテキストに置き換えます。

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

text は UTF-8 文字列。

cursor_begin, cursor_end は、カーソルの先頭と終端の位置で、text の先頭からのバイト数となります。
両方が -1 であれば、カーソルは非表示にします。
両方が同じ値なら、カーソルは線で表示します (次の入力位置)。
異なる値であれば、その間の文字列を強調表示します (どの状況で強調表示するかは、サーバーによる)。
commit_string
void (*commit_string)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, const char *text);

確定した文字列が来ます。
delete_surrounding_text
void (*delete_surrounding_text)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3,
    uint32_t before_length, uint32_t after_length);

入力中、周囲のテキストを削除する必要がある時に来ます。

before_length, after_length は、現在のカーソル位置からの前後のバイト数。
done
void (*done)(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, uint32_t serial);

preedit_string, commit_string, delete_surrounding_text イベントの変更を確定させる時に来ます。

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

serial は、このオブジェクトに対して行った zwp_text_input_v3_commit() の総実行回数と等しくなければなりません。
処理
詳細は、text-input-unstable-v3 の方をご覧ください。

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() で、このサーフェス内でテキストを表示するための、エディタ部分の範囲を指定しています。

入力メソッドで変換が行われた時、変換候補のウィンドウが、この範囲の周囲に表示されます。
実際に表示される位置は、サーバーが決定します。

範囲が設定されていない場合は、サーフェスの位置を元に、適当な位置で表示されます。
動作
text_input # preedit_string | text:"k", cursor_begin:3, cursor_end:3
text_input # done | serial:3
text_input # preedit_string | text:"か", cursor_begin:3, cursor_end:3
text_input # done | serial:3
text_input # preedit_string | text:"かn", cursor_begin:6, cursor_end:6
text_input # done | serial:3
text_input # preedit_string | text:"かん", cursor_begin:6, cursor_end:6
text_input # done | serial:3
text_input # preedit_string | text:"かんj", cursor_begin:9, cursor_end:9
text_input # done | serial:3
text_input # preedit_string | text:"かんじ", cursor_begin:9, cursor_end:9
text_input # done | serial:3
▼ Space キーで変換
text_input # preedit_string | text:"感じ", cursor_begin:0, cursor_end:0
text_input # done | serial:3
▼ 再度 Space キーで、変換候補から選択
text_input # preedit_string | text:"漢字", cursor_begin:0, cursor_end:0
text_input # done | serial:3
▼ Enter
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:3

変換前のテキストでは、カーソルは常に終端位置になります。
変換すると、カーソルは先頭位置になっています。

GNOME では強調表示は行われないようですが、これらの動作はサーバーによって異なります。

Enter キーで入力が確定されたら、commit_string イベントが来ます。

入力中のテキストが確定すると、編集前テキストは NULL になります。