键盘输入
在了解如何使用 XKB 之后,让我们来扩展我们的 Wayland 代码,为我们的键入事件提供输入。与我们获得 wl_pointer
资源的方法类似,我们可以使用 wl_sear.get_keyboard
请求来为一个有着 WL_SEAT_CAPABILITY_KEYBOARD
功能的座位(seat)创建一个 wl_keyboard
。当你创建完成后,你应该发送 "release" 来释放请求:
<request name="release" type="destructor" since="3">
</request>
这将使服务器能够清理与该键盘相关的资源。
但是,你实际上如何使用它呢?让我们从基础知识开始。
键位映射
当你绑定到 wl_keyboard
时,服务端可能发送的第一个事件是 keymap
。
<enum name="keymap_format">
<entry name="no_keymap" value="0" />
<entry name="xkb_v1" value="1" />
</enum>
<event name="keymap">
<arg name="format" type="uint" enum="keymap_format" />
<arg name="fd" type="fd" />
<arg name="size" type="uint" />
</event>
keymap_format
枚举类型是我们想出一种新的 keymaps 格式的情况下提供的(预留),但在本文撰写时,XKB keymaps 仍旧是服务端可能发送的唯一格式。
像这样的批量数据是通过文件描述符传输的。我们可以简单地从文件描述符中读取,但一般来说,建议用 mmap
代替。在 C 语言中,类似可能的实现如下:
#include <sys/mman.h>
// ...
static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard,
uint32_t format, int32_t fd, uint32_t size) {
assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1);
struct my_state *state = (struct my_state *)data;
char *map_shm = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
assert(map_shm != MAP_FAILED);
struct xkb_keymap *keymap = xkb_keymap_new_from_string(
state->xkb_context, map_shm, XKB_KEYMAP_FORMAT_TEXT_V1,
XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(map_shm, size);
close(fd);
// ...do something with keymap...
}
一旦我们有了一个键位映射,我们就可以为这个 wl_keyboard
解释未来的按键事件。请注意,服务端一可以在任何时候发送一个新的键映射,所有未来的按键事件都应该从这个新的映射来解释。
键盘焦点
<event name="enter">
<arg name="serial" type="uint" />
<arg name="surface" type="object" interface="wl_surface" />
<arg name="keys" type="array" />
</event>
<event name="leave">
<arg name="serial" type="uint" />
<arg name="surface" type="object" interface="wl_surface" />
</event>
就像 wl_pointer
里的 "enter" 和 "leave" 事件是当指针在你的表面上移动的时候发出的,服务端在表面收到键盘焦点时发送 wl_keyboard.enter
,而失去焦点的时候发送 wl_keyboard.leave
。许多应用程序会在这些条件下改变它们的外观——比如,开始绘制一个闪烁的光标。
"enter" 事件还包括 array 数组,里面涵盖了当前输入的按键。这是一个由 32 位无符号整数组成的数组,每一个都代表一个所按按键的扫描编码 scancode。
输入事件
一旦将键盘进入你的表面,你就可以期待开始接受输入事件。
<enum name="key_state">
<entry name="released" value="0" />
<entry name="pressed" value="1" />
</enum>
<event name="key">
<arg name="serial" type="uint" />
<arg name="time" type="uint" />
<arg name="key" type="uint" />
<arg name="state" type="uint" enum="key_state" />
</event>
<event name="modifiers">
<arg name="serial" type="uint" />
<arg name="mods_depressed" type="uint" />
<arg name="mods_latched" type="uint" />
<arg name="mods_locked" type="uint" />
<arg name="group" type="uint" />
</event>
"key" 事件在用户按下或者释放一个键的时候被发送。像许多输入事件一样,它包括一个序列,你可以用它来将未来的请求与这个输入事件联系起来。"key" 是所按按键的编码,"state" 是该按键的按下或释放状态。
重要: 这个事件的 scancode 是 Linux evdev scancode。若要将其转换为 XKB 的 scancode,你必须在 evdev scancode 中加 8。
修饰事件包括一个类似的序列,还有按下、锁存和锁定修饰键的掩码,以及当前正在使用的输入组的索引。一个修饰键被按下,就像当你按住 Shift 的时候。修饰键可以锁存,例如启用粘滞键(专为同时按下两个键或多个按键有困难的人而设计的)后先按下一个修饰键 Shift 松开,直到再按下另一个非修饰键时生效。修饰键也可以被锁定,比如当大写锁定开关被打开或关闭时。输入组用于在各种键盘布局之间切换,例如在 ISO 和 ANSI 布局之间,或者用于更多特殊语言的特性。
修饰键的解释因 keymap 而异。你应该把它们都转发给 XKB 来处理。大多数 “修饰键” 事件的实现是非常直接的:
static void wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard,
uint32_t serial, uint32_t depressed, uint32_t latched,
uint32_t locked, uint32_t group) {
struct my_state *state = (struct my_state *)data;
xkb_state_update_mask(state->xkb_state,
depressed, latched, locked, 0, 0, group);
}
按键重复
最后让我们来考虑 "repeat_info" 事件:
<event name="repeat_info" since="4">
<arg name="rate" type="int" />
<arg name="delay" type="int" />
</event>
在 Wayland 中,客户端负责实现 “按键重复”——只要你按住按键,就会持续输入字符的功能。发送这个事件是为了将用户对重复事设置的偏好通知给客户端。延迟 "delay" 是指在按键重复启动前的需要保持按下的毫秒数,速率 "rate" 是指直到按键被释放每秒重复输入的字符数。