キーボード入力
今回は、キーボード入力を行います。
必要なパッケージ
キーマップの処理に xkbcommon を使うので、開発用パッケージをインストールしてください。Debian/Ubuntu | libxkbcommon-dev |
---|---|
RedHat | libxkbcommon-devel |
プログラム
$ cc -o test 10-keyboard.c client.c imagebuf.c xdg-shell-protocol.c -lwayland-client -lrt -lxkbcommon
※libxkbcommon をリンクする必要があります。
押されたキーなどの情報を出力します。
ウィンドウがアクティブな状態で ESC キーが押されたら、終了します。
<10-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 <wayland-client.h> #include "xdg-shell-client-protocol.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; }client_ex; //------------- /* キーボード関連解放 */ static void _keyboard_release(client_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->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(client_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((client_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) { client_ex *p = (client_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->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) { client_ex *p = (client_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 }; //=================== /* wl_seat:capabilities */ static void _seat_capabilities(client *cl,struct wl_seat *seat,uint32_t cap) { client_ex *p = (client_ex *)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); } } /* main */ int main(void) { client_ex *p; Toplevel *win; p = (client_ex *)client_new(sizeof(client_ex)); p->b.init_flags = CLIENT_INIT_FLAGS_SEAT; p->b.seat_capabilities = _seat_capabilities; client_init(CLIENT(p)); // win = toplevel_create(CLIENT(p), 256, 256, NULL); imagebuf_fill(win->sf.img, 0xffff0000); // client_loop_simple(CLIENT(p)); //解放 toplevel_destroy(win); _keyboard_release(p); client_destroy(CLIENT(p)); return 0; }
wl_keyboard
キーボード操作を行うためには wl_keyboard が必要なので、wl_pointer の時と同じように、wl_seat のバインド後、wl_seat の capabilities イベント内で、作成と解放の処理を行います。
wl_keyboard のイベント
struct wl_keyboard_listener { void (*keymap)(void *data, struct wl_keyboard *wl_keyboard, uint32_t format, int32_t fd, uint32_t size); void (*enter)(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface, struct wl_array *keys); void (*leave)(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface); void (*key)(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state); void (*modifiers)(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group); //ver 4 void (*repeat_info)(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay); };
keymap | キーマップの設定を行う時 |
---|---|
enter | キーボードフォーカスが来た時。 (クライアント内のウィンドウがアクティブになって、キー入力ができる状態になった時) |
leave | キーボードフォーカスが離れた時 |
key | キーが押された or 離された時 |
modifiers | 装飾キーが押された/離された、または、ロックの状態が変化した時 |
repeat_info | (ver 4) キーリピートの設定が通知される |
キーマップ
「キーマップ」とは、キーボードの物理的なキー情報から、プログラムなどによって共通で認識できるコードへと変換をするための、割り当てのことです。
例えば、ハードウェア上の 0 番のボタンは、ESC キーに割り当てる、といった感じの情報です。
キーボードの配列は、英語や日本語などの言語ごとに異なるため、日本語キーボードを使っているのに、英語キーボードの配列設定になっていたりすると、正しく入力が行なえません。
(押したキーと、実際に出力された文字が一致しないなど)
Wayland でキーボードを操作する場合、キーマップを処理するライブラリを使って、ハードウェアのキー情報から、共通の一般的なキーコードへと変換する処理を行う必要があります。
現在 Wayland では、キーマップのフォーマットとして「XKB (ver 1)」のみ対応しています。
XKB を使うためのライブラリとして、xkbcommon を使います。
ヘッダファイルは /usr/include/xkbcommon ディレクトリ下にあり、xkbcommon.h をメインのインクルードファイルとして使います。
libxkbcommon をリンクする必要があります。
例えば、ハードウェア上の 0 番のボタンは、ESC キーに割り当てる、といった感じの情報です。
キーボードの配列は、英語や日本語などの言語ごとに異なるため、日本語キーボードを使っているのに、英語キーボードの配列設定になっていたりすると、正しく入力が行なえません。
(押したキーと、実際に出力された文字が一致しないなど)
Wayland でキーボードを操作する場合、キーマップを処理するライブラリを使って、ハードウェアのキー情報から、共通の一般的なキーコードへと変換する処理を行う必要があります。
XKB
XKB は、X11 において、より詳細なキーボード処理を行うための拡張機能として整備されたものですが、Wayland でも、その仕様をそのまま使えるようになっています。現在 Wayland では、キーマップのフォーマットとして「XKB (ver 1)」のみ対応しています。
XKB を使うためのライブラリとして、xkbcommon を使います。
ヘッダファイルは /usr/include/xkbcommon ディレクトリ下にあり、xkbcommon.h をメインのインクルードファイルとして使います。
libxkbcommon をリンクする必要があります。
keymap イベント
wl_keyboard の keymap イベントは、キーマップの設定を行う必要がある時に呼ばれます。
キーボードを使う時は、最初にキーマップの設定が必要になるので、キーボードが有効になった時か、キーマップ設定が変更された時に呼ばれます。
引数 format は、設定するキーマップのフォーマットです。
現在設定可能なのは、XKB (ver 1) だけです。
fd, size は、キーマップの設定情報が入った共有メモリの、ファイルディスクリプタとデータサイズです。
渡された fd は、mmap() を使ってメモリにマッピングした後、バッファの中身を文字列データとして使います。
設定が終わったら、アンマップしてクローズします。
XKB の場合は、以下のような感じで、キーの設定情報が入っています。
キーボードを使う時は、最初にキーマップの設定が必要になるので、キーボードが有効になった時か、キーマップ設定が変更された時に呼ばれます。
ハンドラ処理
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((client_ex *)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 のキーマップを作成します。
まず、xkb_context_new() で、キーマップの作成に必要なコンテキスト (xkb_context) を作成します。
次に、xkb_keymap_new_from_string() で、文字列の設定データからキーマップ (xkb_keymap) を作成します。
あとは、xkb_state_new() で、キーボードの現在状態を管理するための xkb_state を作成します。
xkb_*_unref() は、オブジェクトの参照カウンタをデクリメントし、カウンタが 0 になったら解放します。
xkb_context は、キーマップを作成したら必要なくなるので、ここで解放します。
xkb_keymap と xkb_state のポインタは保持しておきます。
(キーマップが変更された場合は、先に、現在使われているポインタを解放しています。
unref 関数は NULL ポインタを判定してくれるので、新規状態でも問題なく動作します)
ちなみに、Wayland 関数の wl_*_destroy() などの場合は、NULL ポインタを判定してくれません。
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_keymap と xkb_state のポインタは保持しておきます。
(キーマップが変更された場合は、先に、現在使われているポインタを解放しています。
unref 関数は NULL ポインタを判定してくれるので、新規状態でも問題なく動作します)
ちなみに、Wayland 関数の 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) { client_ex *p = (client_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->b.finish_loop = 1; }
引数
key は、linux/input-event-codes.h で定義されているキーコードです。
物理的なキーコードではなく、Linux の evdev によるキーコードのため、一般的なキーであれば、どのキーが押されたかということは判別できます。
state は、押されたか離されたかの状態です。
物理的なキーコードではなく、Linux の evdev によるキーコードのため、一般的なキーであれば、どのキーが押されたかということは判別できます。
state は、押されたか離されたかの状態です。
WL_KEYBOARD_KEY_STATE_RELEASED = 0 WL_KEYBOARD_KEY_STATE_PRESSED = 1
キーコード変換
XKB を使って、key のキーコードから、一般的に使用するキーの識別子に変換したい場合、xkb_state_key_get_one_sym() を使います。
Shift や NumLock などのキーの現在の状態と、key 引数で指定されたキーを組み合わせて、実際に文字として表示したりするためのキーコードを取得できます。
戻り値として、変換されたキーの識別子が返ります。
XKB のキー識別子 (XKB_KEY_*) は、xkbcommon-keysyms.h で定義されています。
※xkb_state_key_get_one_sym() にキーコードを渡す時は、key 引数の値を +8 する必要があります。
これは、Linux の evdev のキーコードを、XKB のキーコードに合わせる際に必要になります。
Shift や NumLock などのキーの現在の状態と、key 引数で指定されたキーを組み合わせて、実際に文字として表示したりするためのキーコードを取得できます。
戻り値として、変換されたキーの識別子が返ります。
XKB のキー識別子 (XKB_KEY_*) は、xkbcommon-keysyms.h で定義されています。
※xkb_state_key_get_one_sym() にキーコードを渡す時は、key 引数の値を +8 する必要があります。
これは、Linux の evdev のキーコードを、XKB のキーコードに合わせる際に必要になります。
例
例えば、'A' キーを押した場合、キーコードは「30 (KEY_A)」、keysym は「0x61 (XKB_KEY_a)」となります。
Shift キーと同時に 'A' キーを押した場合、キーコードは「30 (KEY_A)」で同じですが、keysym は「0x41 (XKB_KEY_A)」になります。
Shift キーと同時に 'A' キーを押した場合、キーコードは「30 (KEY_A)」で同じですが、keysym は「0x41 (XKB_KEY_A)」になります。
modifiers イベント
Shift や NumLock などの修飾キーの状態が変化すると、modifiers イベントが呼ばれます。
装飾キーが押したり離された時や、NumLock などが ON/OFF された時に来ます。
まず、xkb_state_update_mask() を使い、modifiers イベントに渡された値をそのまま指定して、装飾キーなどの状態を更新します。
どの状態が変更されたかを取得したい場合は、xkb_state_serialize_mods() で、現在の状態のフラグ値を取得します。
xkb_keymap_mod_get_index() を使うと、指定名のキーのビット位置を取得できます。
各名前は xkbcommon-names.h で定義されています。
指定キーのビットが ON であれば、そのキーは ON の状態となります。
※wl_pointer のイベントでは、Shift や Ctrl の装飾キーが現在押されているかどうかを判別できないため、このイベントで装飾キーの状態を取得して、記録しておく必要があります。
装飾キーが押したり離された時や、NumLock などが ON/OFF された時に来ます。
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) { client_ex *p = (client_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 "); ... }
まず、xkb_state_update_mask() を使い、modifiers イベントに渡された値をそのまま指定して、装飾キーなどの状態を更新します。
どの状態が変更されたかを取得したい場合は、xkb_state_serialize_mods() で、現在の状態のフラグ値を取得します。
xkb_keymap_mod_get_index() を使うと、指定名のキーのビット位置を取得できます。
各名前は xkbcommon-names.h で定義されています。
指定キーのビットが ON であれば、そのキーは ON の状態となります。
※wl_pointer のイベントでは、Shift や Ctrl の装飾キーが現在押されているかどうかを判別できないため、このイベントで装飾キーの状態を取得して、記録しておく必要があります。
repeat_info イベント (ver 4)
void (*repeat_info)(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay);
repeat_info イベントでは、キーリピートの設定情報が通知されます。
キーリピートは、キーが押し続けられている間、一定間隔でそのキーが押されているものとして扱う処理のことです。
'A' キーを押した直後は 'A' を出力し、一定時間を過ぎたら、一定間隔ごとに 'A' を繰り返し出力する、というようなことです。
サーバーはキーリピートを処理しないため、必要に応じて、クライアントが自分で実装する必要があります。
実装方法
実際の実装方法としては、以下のようになります。
- キーが押された時、キーリピートの判定を開始する。
- 押されてから一定時間が経過したら、キーリピートを開始する。
- 一定時間が経過するごとに、同じキーが再度押されたとみなす。
- キーが離されるまで、繰り返す。
設定値
上記の実装において、設定として、2つの時間値が指定可能であり、クライアントは独自の時間間隔を使うこともできますが、ユーザーがデスクトップ側で設定した値を使いたい場合は、このイベントで送られてきた値を使います。
2つとも、単位はミリ秒 (ms) です。
GNOME の場合は、デフォルトで「delay = 500」「rate = 33」となっています。
delay | 最初にキーが押された時間から、キーリピートを開始するまでの時間 |
---|---|
rate | キーリピートする間隔 |
2つとも、単位はミリ秒 (ms) です。
GNOME の場合は、デフォルトで「delay = 500」「rate = 33」となっています。