Wayland - キーボード入力

キーボード入力
ここでは、キーボード入力を行います。

必要なパッケージ
キーマップの処理に xkbcommon を使うので、開発用パッケージをインストールしてください。

Debian/Ubuntulibxkbcommon-dev
RedHatlibxkbcommon-devel
プログラム
$ cc -o test 09_keyboard.c client.c imagebuf.c -lwayland-client -lrt -lxkbcommon

libxkbcommon をリンクする必要があります。

09_keyboard.c
/******************************
 * キーボード入力
 *
 * ESC キーで終了。
 ******************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#include <wayland-client.h>

#include <xkbcommon/xkbcommon.h>

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


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

typedef struct
{
    Client b;

    struct wl_keyboard *keyboard;
    struct xkb_keymap *xkb_keymap;
    struct xkb_state *xkb_state;
}ClientEx;

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


/* キーボード関連解放 */

static void _keyboard_release(ClientEx *p)
{
    //XKB 解放

    xkb_keymap_unref(p->xkb_keymap);
    xkb_state_unref(p->xkb_state);

    p->xkb_keymap = NULL;
    p->xkb_state = NULL;

    //wl_keyboard

    if(p->b.seat_ver >= WL_KEYBOARD_RELEASE_SINCE_VERSION)
        wl_keyboard_release(p->keyboard);
    else
        wl_keyboard_destroy(p->keyboard);

    p->keyboard = NULL;
}

/* XKB キーマップ作成 */

static void _xkb_keymap(ClientEx *p,char *mapstr)
{
    struct xkb_context *context;
    struct xkb_keymap *keymap;

    //コンテキスト作成

    context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
    if(!context) return;

    //キーマップ作成

    keymap = xkb_keymap_new_from_string(context,
            mapstr, XKB_KEYMAP_FORMAT_TEXT_V1, 0);

    if(!keymap)
    {
        xkb_context_unref(context);
        return;
    }

    //

    xkb_keymap_unref(p->xkb_keymap);
    xkb_state_unref(p->xkb_state);

    p->xkb_keymap = keymap;
    p->xkb_state = xkb_state_new(keymap);

    //

    xkb_context_unref(context);
}


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


/* キーマップ */

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

    printf("keymap | format:%u, fd:%d, size:%u\n", format, fd, size);

    //XKB ver 1 以外は対応しない

    if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
    {
        close(fd);
        return;
    }

    //文字列としてメモリにマッピング

    mapstr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);

    if(mapstr == MAP_FAILED)
    {
        close(fd);
        return;
    }

    //キーマップ作成

    _xkb_keymap((ClientEx *)data, mapstr);

    //

    munmap(mapstr, size);
    close(fd);
}

/* キーボードフォーカスが来た時 */

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

/* キーボードフォーカスが離れた時 */

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

/* キーが押された/離された時 */

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

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

    //現在の状態とキーコードから keysym (XKB_KEY_*) を取得

    sym = xkb_state_key_get_one_sym(p->xkb_state, key + 8);

    printf("-- xkb keyshm (0x%X)\n", sym);

    //ESC キーで終了

    if(sym == XKB_KEY_Escape)
        p->b.finish_loop = 1;
}

/* 装飾キーやロックの状態が変化した時 */

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)
{
    ClientEx *p = (ClientEx *)data;
    xkb_mod_mask_t mods;

    printf("modifiers | depressed:%u, latched:%u, locaked:%u, group:%u\n",
        mods_depressed, mods_latched, mods_locked, group);

    //状態をセット

    xkb_state_update_mask(p->xkb_state,
        mods_depressed, mods_latched, mods_locked, group, 0, 0);

    //現在の状態のフラグ取得

    mods = xkb_state_serialize_mods(p->xkb_state, XKB_STATE_MODS_EFFECTIVE);

    //状態表示

    printf("-- mods: ");

    if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_CTRL)))
        printf("Ctrl ");

    if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_SHIFT)))
        printf("Shift ");

    if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_ALT)))
        printf("Alt ");

    if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_LOGO)))
        printf("Logo ");

    if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_NUM)))
        printf("NumLock ");

    if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_CAPS)))
        printf("CapsLock ");

    printf("\n");
}

/* キーリピートの情報 */

static void _keyboard_repeat_info(void *data, struct wl_keyboard *keyboard,
    int32_t rate, int32_t delay)
{
    printf("repeat_info | rate:%d, delay:%d\n", rate, delay);
}

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


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


/* wl_seat:capabilities */

static void _seat_capabilities(Client *cl,struct wl_seat *seat,uint32_t cap)
{
    ClientEx *p = (ClientEx *)cl;

    if((cap & WL_SEAT_CAPABILITY_KEYBOARD) && !p->keyboard)
    {
        //wl_keyboard 作成

        p->keyboard = wl_seat_get_keyboard(cl->seat);

        wl_keyboard_add_listener(p->keyboard, &g_keyboard_listener, p);
    }
    else if(!(cap & WL_SEAT_CAPABILITY_KEYBOARD) && p->keyboard)
    {
        //wl_keyboard 解放

        _keyboard_release(p);
    }
}


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


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

    p = (ClientEx *)Client_new(sizeof(ClientEx));

    p->b.init_flags = INIT_FLAGS_SEAT;
    p->b.seat_capabilities = _seat_capabilities;

    Client_init(CLIENT(p));

    //

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

    ImageBuf_fill(win->img, 0xffff0000);
    Window_update(win);

    //

    Client_loop_simple(CLIENT(p));

    //解放

    Window_destroy(win);

    _keyboard_release(p);
    
    Client_destroy(CLIENT(p));

    return 0;
}

ウィンドウがアクティブな状態で、ESC キーが押されたら、終了します。
wl_keyboard
キーボード操作を行うためには wl_keyboard が必要なので、wl_pointer の時と同じように、wl_seat のバインド後、capabilities イベント内で、作成と解放の処理を行います。

wl_keyboard のイベント
keymapキーマップの設定を行う時
enterキーボードフォーカスが来た時。
(クライアント内のウィンドウがアクティブになって、キー入力ができる状態になった時)
leaveキーボードフォーカスが離れた時
keyキーが押された or 離された時
modifiers装飾キーが押された/離された、または、ロックの状態が変化した時
repeat_infoキーリピートの設定が通知される
キーマップ
キーマップ」は、キーボードの、ハードウェアのキー情報から、プログラムなどで認識可能なコードへと変換をする、その割り当てのことです。

ハードウェア上の 0 番のボタンは ESC キーに割り当てる、といった感じの情報です。

キーボードの配列は言語ごとに異なるため、日本語キーボードを使っているのに英語キーボードの配列設定になっていたりすると、正しく入力が行なえません。
(押したキーと実際に入力されたキーが一致しない)

Wayland でキーボードを操作する場合は、キーマップを処理するライブラリを使って、ハードウェアのキー情報から、プログラムで使いやすいキーコードへと変換する処理を行う必要があります。

XKB
現在 Wayland では、キーマップのフォーマットは「XKB (ver 1)」のみ対応しています。
XKB を使うためのライブラリとして、xkbcommon を使います。

ヘッダファイルは、/usr/include/xkbcommon ディレクトリ下のファイルを使います。
(メインは xkbcommon.h)。

libxkbcommon をリンクして使います。
keymap イベント
wl_keyboard の keymap イベントは、キーマップの設定を行う必要がある時に呼ばれます。

キーボードを使う時は、最初にキーマップの設定が必要なので、キーボードが有効になった時か、キーマップ設定が変更された時に呼ばれます。

ハンドラ処理
では、keymap イベントハンドラ内の処理を見てみます。

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

    printf("keymap | format:%u, fd:%d, size:%u\n", format, fd, size);

    //XKB ver 1 以外は対応しない

    if(format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1)
    {
        close(fd);
        return;
    }

    //文字列としてメモリにマッピング

    mapstr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);

    if(mapstr == MAP_FAILED)
    {
        close(fd);
        return;
    }

    //キーマップ作成

    _xkb_keymap((ClientEx *)data, mapstr);

    //

    munmap(mapstr, size);
    close(fd);
}

引数 format は、設定するキーマップのフォーマットです。
現在設定可能なのは、XKB (ver 1) だけです。

WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP = 0
WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 = 1

fd, size は、キーマップの設定情報が入った共有メモリの、ファイルディスクリプタとデータサイズです。

渡された fd は、mmap() を使ってメモリにマッピングして、中身を文字列データとして使います。
設定が終わったら、アンマップしてクローズします。

XKB の場合は、以下のような感じで設定情報が入っています。

xkb_keymap {
xkb_keycodes "(unnamed)" {
    minimum = 8;
    maximum = 255;
    <ESC>                = 9;
    <AE01>               = 10;
    <AE02>               = 11;
...
XKB キーマップ作成
渡されたキーマップ設定の文字列から、XKB のキーマップを作成します。

struct xkb_context *context;
struct xkb_keymap *keymap;

//コンテキスト作成

context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
if(!context) return;

//キーマップ作成

keymap = xkb_keymap_new_from_string(context,
        mapstr, XKB_KEYMAP_FORMAT_TEXT_V1, 0);

if(!keymap)
{
    xkb_context_unref(context);
    return;
}

//

xkb_keymap_unref(p->xkb_keymap);
xkb_state_unref(p->xkb_state);

p->xkb_keymap = keymap;
p->xkb_state = xkb_state_new(keymap);

//

xkb_context_unref(context);

まず、xkb_context_new() で、キーマップの作成に必要なコンテキスト (xkb_context) を作成します。

次に、xkb_keymap_new_from_string() で、文字列の設定データからキーマップ (xkb_keymap) を作成します。

あとは、xkb_state_new() で、キーボードの現在状態を管理するための xkb_state を作成します。

xkb_*_unref() は、オブジェクトの参照カウンタをデクリメントし、カウンタが 0 になったら解放します。

xkb_context は、キーマップを作成したら必要なくなるので、ここで解放します。

xkb_keymapxkb_state のポインタは保持しておきます。
(キーマップが変更された時は、先に、現在使われているポインタを解放しています。
unref 関数は NULL ポインタを判定してくれるので、新規状態でも問題なく動作します)

ちなみに、wl_*_destroy() の場合は、NULL ポインタを判定してくれません。
key イベント
キーが押されたり離された時は、key イベントが呼ばれます。

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

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

    //現在の状態とキーコードから keysym (XKB_KEY_*) を取得

    sym = xkb_state_key_get_one_sym(p->xkb_state, key + 8);

    printf("-- xkb keyshm (0x%X)\n", sym);

    //ESC キーで終了

    if(sym == XKB_KEY_Escape)
        p->b.finish_loop = 1;
}

引数
key は、物理キー番号です。
linux/input-event-codes.h で定義されているキーコードとなります。

state は、押されたか離されたかの状態です。

WL_KEYBOARD_KEY_STATE_RELEASED = 0
WL_KEYBOARD_KEY_STATE_PRESSED = 1

キーコード変換
XKB でキーコードを変換したい場合、xkb_state_key_get_one_sym() を使います。

Shift や NumLock など、現在の装飾キーなどの状態と、引数で指定されたキーを組み合わせて、実際に文字として表示したりするキーを取得できます。

戻り値として、変換されたキーの識別子が返ります。
XKB のキー識別子 (XKB_KEY_*) は、xkbcommon-keysyms.h で定義されています。

キーコードを引数として渡す時に +8 していますが、なぜ 8 なのかはよくわかりません。
ただ、物理キーコードを渡す時は、常に +8 しなければならないようなので、そうしています。

例えば、'A' キーを押すと、物理キーコードは「30 (KEY_A)」、keysym は「0x61 (XKB_KEY_a)」となります。

Shift キーと同時に 'A' キーを押すと、物理キーコードは「30 (KEY_A)」で同じですが、keysym は「0x41 (XKB_KEY_A)」になります。
modifiers イベント
Shift や NumLock などのキーの状態が変化すると、modifiers イベントが呼ばれます。

装飾キーが押したり離された時や、NumLock などが ON/OFF された時に来ます。

//状態をセット

xkb_state_update_mask(p->xkb_state,
    mods_depressed, mods_latched, mods_locked, group, 0, 0);

//現在の状態のフラグ取得

xkb_mod_mask_t mods;

mods = xkb_state_serialize_mods(p->xkb_state, XKB_STATE_MODS_EFFECTIVE);

//

if(mods & (1 << xkb_keymap_mod_get_index(p->xkb_keymap, XKB_MOD_NAME_CTRL)))
    printf("Ctrl ");

まず、xkb_state_update_mask() を使い、渡された引数をそのまま渡して、装飾キーなどの状態を更新します。

どの状態が変更されたかを取得したい場合は、xkb_state_serialize_mods() で、現在の状態のフラグ値を取得します。

xkb_keymap_mod_get_index() を使うと、指定名のキーのビット位置を取得できます。
各名前は xkbcommon-names.h で定義されています。

指定キーのビットが ON であれば、そのキーは ON の状態となります。
repeat_info イベント
repeat_info イベントでは、キーリピートの設定情報が通知されてきます。
※ ver 4 から使用できます。

key イベントは、キーを押し続けている間は何も起こらない (サーバーでキーリピートが処理されない) ため、キーリピートを行いたい場合は、クライアントが自分で実装する必要があります。

実装方法としては、以下のようになります。

  1. キーが押された時、キーリピートの判定を開始する
  2. 押されてから一定時間経過したら、キーリピートを開始
  3. 一定間隔を経過するごとに、同じキーが再度押されたとみなす
  4. キーが離されるまで繰り返す

Wayland デスクトップ側でキーリピートの値を設定し、すべてのクライアントに適用させたい場合は、このイベントで間隔の情報が送られてきます。
単位はミリ秒 (ms) です。

delayキーが押された時点から、キーリピートを開始するまでの時間
rateキーリピートの間隔

weston で実行すると、「rate = 40」「delay = 400」でした。
GNOME では、デフォルトで「rate = 33」「delay = 500」でした。