キーボード入力
ここでは、キーボード入力を行います。
必要なパッケージ
キーマップの処理に xkbcommon を使うので、開発用パッケージをインストールしてください。Debian/Ubuntu | libxkbcommon-dev |
---|---|
RedHat | libxkbcommon-devel |
プログラム
ウィンドウがアクティブな状態で ESC キーが押されたら、終了します。
※ libxkbcommon をリンクする必要があります。
<09_keyboard.c>
$ 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; }
wl_keyboard
キーボード操作を行うためには wl_keyboard が必要なので、wl_pointer の時と同じように、wl_seat のバインド後、capabilities イベント内で、作成と解放の処理を行います。
wl_keyboard のイベント
keymap | キーマップの設定を行う時 |
---|---|
enter | キーボードフォーカスが来た時。 (クライアント内のウィンドウがアクティブになって、キー入力ができる状態になった時) |
leave | キーボードフォーカスが離れた時 |
key | キーが押された or 離された時 |
modifiers | 装飾キーが押された/離された、または、ロックの状態が変化した時 |
repeat_info | キーリピートの設定が通知される |
キーマップ
「キーマップ」は、キーボードの、ハードウェアのキー情報から、プログラムなどで認識可能なコードへと変換をする、その割り当てのことです。
ハードウェア上の 0 番のボタンは ESC キーに割り当てる、といった感じの情報です。
キーボードの配列は言語ごとに異なるため、日本語キーボードを使っているのに英語キーボードの配列設定になっていたりすると、正しく入力が行なえません。
(押したキーと実際に入力されたキーが一致しない)
Wayland でキーボードを操作する場合は、キーマップを処理するライブラリを使って、ハードウェアのキー情報から、プログラムで使いやすいキーコードへと変換する処理を行う必要があります。
XKB を使うためのライブラリとして、xkbcommon を使います。
ヘッダファイルは、/usr/include/xkbcommon ディレクトリ下のファイルを使います。
(メインは xkbcommon.h)。
libxkbcommon をリンクして使います。
ハードウェア上の 0 番のボタンは ESC キーに割り当てる、といった感じの情報です。
キーボードの配列は言語ごとに異なるため、日本語キーボードを使っているのに英語キーボードの配列設定になっていたりすると、正しく入力が行なえません。
(押したキーと実際に入力されたキーが一致しない)
Wayland でキーボードを操作する場合は、キーマップを処理するライブラリを使って、ハードウェアのキー情報から、プログラムで使いやすいキーコードへと変換する処理を行う必要があります。
XKB
現在 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((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 のキーマップを作成します。
まず、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 ポインタを判定してくれるので、新規状態でも問題なく動作します)
ちなみに、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 ポインタを判定してくれるので、新規状態でも問題なく動作します)
ちなみに、wl_*_destroy() の場合は、NULL ポインタを判定してくれません。
key イベント
キーが押されたり離された時は、key イベントが呼ばれます。
state は、押されたか離されたかの状態です。
Shift や NumLock など、現在の装飾キーなどの状態と、引数で指定されたキーを組み合わせて、実際に文字として表示したりするキーを取得できます。
戻り値として、変換されたキーの識別子が返ります。
XKB のキー識別子 (XKB_KEY_*) は、xkbcommon-keysyms.h で定義されています。
※キーコードを引数として渡す時は、+8 する必要があります。これは、evdev のキーコードを、XKB のキーコードに合わせる際に必要になります。
Shift キーと同時に 'A' キーを押すと、キーコードは「30 (KEY_A)」で同じですが、keysym は「0x41 (XKB_KEY_A)」になります。
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 する必要があります。これは、evdev のキーコードを、XKB のキーコードに合わせる際に必要になります。
例
例えば、'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() を使い、渡された引数をそのまま渡して、装飾キーなどの状態を更新します。
どの状態が変更されたかを取得したい場合は、xkb_state_serialize_mods() で、現在の状態のフラグ値を取得します。
xkb_keymap_mod_get_index() を使うと、指定名のキーのビット位置を取得できます。
各名前は xkbcommon-names.h で定義されています。
指定キーのビットが ON であれば、そのキーは ON の状態となります。
装飾キーが押したり離された時や、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 イベントは、キーを押し続けている間は何も起こらない (サーバーでキーリピートが処理されない) ため、キーリピートを行いたい場合は、クライアントが自分で実装する必要があります。
実装方法としては、以下のようになります。
Wayland デスクトップ側でキーリピートの値を設定し、すべてのクライアントに適用させたい場合は、このイベントで間隔の情報が送られてきます。
単位はミリ秒 (ms) です。
weston で実行すると、「rate = 40」「delay = 400」でした。
GNOME では、デフォルトで「rate = 33」「delay = 500」でした。
※ ver 4 から使用できます。
key イベントは、キーを押し続けている間は何も起こらない (サーバーでキーリピートが処理されない) ため、キーリピートを行いたい場合は、クライアントが自分で実装する必要があります。
実装方法としては、以下のようになります。
- キーが押された時、キーリピートの判定を開始する
- 押されてから一定時間経過したら、キーリピートを開始
- 一定間隔を経過するごとに、同じキーが再度押されたとみなす
- キーが離されるまで繰り返す
Wayland デスクトップ側でキーリピートの値を設定し、すべてのクライアントに適用させたい場合は、このイベントで間隔の情報が送られてきます。
単位はミリ秒 (ms) です。
delay | キーが押された時点から、キーリピートを開始するまでの時間 |
---|---|
rate | キーリピートの間隔 |
weston で実行すると、「rate = 40」「delay = 400」でした。
GNOME では、デフォルトで「rate = 33」「delay = 500」でした。