Wayland - キーボード入力

キーボード入力
キーボード入力を行います。
ESC キーが押されたら終了します。

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

Debian/Ubuntu : libxkbcommon-dev
RedHat : libxkbcommon-devel

コンパイル
> common1.h
> common1.c

libxkbcommon を追加リンクしています。

$ cc -o test 09_keyboard.c common1.c -lwayland-client -lrt -lxkbcommon

ソースコード
<09_keyboard.c>
#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 <xkbcommon/xkbcommon.h>

#include "common1.h"

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

typedef struct
{
    wayland base;
    int quit_flag;

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

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


/* キーボード解放 */

static void _keyboard_release(wayland_ex *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->base.seat_version >= WL_KEYBOARD_RELEASE_SINCE_VERSION)
        wl_keyboard_release(p->keyboard);
    else
        wl_keyboard_destroy(p->keyboard);

    p->keyboard = NULL;
}

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

static void _xkb_keymap(wayland_ex *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((wayland_ex *)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)
{
    wayland_ex *p = (wayland_ex *)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->quit_flag = 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)
{
    wayland_ex *p = (wayland_ex *)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
};


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


static void _seat_capabilities(wayland *wl,struct wl_seat *seat,uint32_t cap)
{
    wayland_ex *p = (wayland_ex *)wl;

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

        p->keyboard = wl_seat_get_keyboard(wl->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)
{
    wayland_ex *wl;
    window *win;

    wl = (wayland_ex *)wayland_new(sizeof(wayland_ex));

    wl->base.init_flags = INIT_FLAGS_SEAT;
    wl->base.handle_seat_capabilities = _seat_capabilities;

    wayland_init(WAYLAND_PTR(wl));

    //

    win = window_create(WAYLAND_PTR(wl), 256, 256);

    imagebuf_fill(win->img, 0xff0000);
    window_update(win);

    //

    while(wl_display_dispatch(wl->base.display) != -1
        && wl->quit_flag == 0);

    //解放

    window_destroy(win);

    _keyboard_release(wl);

    wayland_destroy(WAYLAND_PTR(wl));

    return 0;
}
解説
wl_keyboard
wl_pointer の時と同じように、wl_seat をバインドして、wl_seat の capabilities ハンドラ内で、wl_keyboard の作成と、デバイスが使えなくなった時の解放処理をします。

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

    p->keyboard = wl_seat_get_keyboard(wl->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);
}

解放時は、wl_seat のバージョンが 3 以降の場合は wl_keyboard_release() を使い、それ未満の場合は wl_keyboard_desyroy() を使います。
wl_keyboard のイベントハンドラ一覧
keymapキーマップの設定を行う時
enterキーボードフォーカスが来た時
(クライアント内のウィンドウがアクティブになって、キー入力ができる状態になった時)
leaveキーボードフォーカスが離れた時
keyキーが押された or 離された時
modifiers装飾キーが押された/離された、またはロックの状態が変化した時
repeat_infoキーリピートの設定が通知される
キーマップ
キーマップは、キーボードのハードウェアのキー情報から、認識可能なコードへ変換する、割り当てのことです。
0 番のボタンは ESC キーに割り当てる、といった感じです。

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

Wayland の場合は、キーマップを処理するライブラリを使って、自分でキーコードの変換などの処理を行う必要があります。
現在、Wayland では XKB (ver 1) のみ対応可能となっているので、そのライブラリとして xkbcommon を使います。

ヘッダファイルは、/usr/include/xkbcommon ディレクトリ下のファイル (メインは xkbcommon.h)。
ライブラリは libxkbcommon.so を使います。
keymap イベント
keymap イベントは、キーマップの設定を行う必要がある時に呼ばれます。
キーボードを使う時は最初にキーマップの設定が必要なので、キーボードが有効になった時と、キーマップ設定が変更された時に来ます。

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

static void _keyboard_keymap(void *data, struct wl_keyboard *wl_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((wayland_ex *)data, mapstr);

    //

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

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

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 ポインタを判定してくれるので、NULL だった場合でも問題なく動作します。

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

static void _keyboard_key(void *data, struct wl_keyboard *wl_keyboard,
    uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
    wayland_ex *p = (wayland_ex *)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->quit_flag = 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 の xkb_state_update_mask() を使って、ハンドラに渡された引数をそのまま渡して、装飾キーなどの状態を更新します。

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

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

取得したフラグ値で、指定キーのビットが立っているかを判定すると、現在そのキーが ON の状態かを取得できます。
repeat_info イベント
repeat_info イベントは、クライアントにキーリピートの設定情報を通知するために来ます。
wl_seat のバージョンが 4 以降で使用できます。

キーリピートの処理は、クライアントが行わなければなりません。

key イベントは、キーを押し続けている間は何も起こらず、キーを押した瞬間しかイベントが出力されません。
なので、キーリピートを行う場合は、「キーが押された時、キーリピートの判定を開始し、指定時間経過したらキーリピートを開始。
キーが離されるまでの間、同じキーを繰り返し出力する」、という形で実装する必要があります。

そのため、キーリピートの設定情報として、以下の値が、ms 単位の数値で送られてきます。
「キーが押された時からキーリピートを開始するまでの時間」 (delay)
「キーリピート中に同じキーを出力する時の間隔」 (rate)

weston で実行した時は、rate が 40、delay が 400 の値になっていました。
GNOME では、デフォルトで rate が 33、delay が 500 でした。

wl_keyboard を作成した時、一番最初にこのイベントが生成されます。
また、サーバー側でキーリピートの設定が変更された時にも来ます。