如何在xcb-xkb中追踪键盘布局切换时的键盘变化?
问题描述
我通过setxkbmap -layout us,gr -option grp:alt_shift_toggle配置了美式英语与希腊语键盘布局,使用Alt + Shift快捷键切换。编写了一段基于XCB/XKB的代码(编译命令:cc *.c -lxcb -lxcb-xkb -lxkbcommon -lxkbcommon-x11)检测布局切换,原本预期切换时会收到NewKeyboardNotify事件(类似手动执行setxkbmap -layout us或setxkbmap -layout gr时的行为),但实际每次切换都会收到一对事件:IndicatorStateNotify和ExtensionDeviceNotify,对此存在疑问,希望了解原因及事件解读方式。
代码如下:
#include <stdio.h> #include <stdlib.h> #include <xcb/xcb.h> #include <xcb/xkb.h> struct display { xcb_connection_t *xcb; uint8_t xkb_base_event, xkb_base_error; int32_t keyboard_device_id; } display; void open_display(void) { int connection_error; xcb_xkb_use_extension_cookie_t xkb_cookie; xcb_xkb_get_device_info_cookie_t device_cookie; const xcb_query_extension_reply_t *extension_reply; xcb_xkb_use_extension_reply_t *xkb_reply; xcb_xkb_get_device_info_reply_t *device_reply; xcb_generic_error_t *error; display.xcb = xcb_connect(NULL, NULL); xcb_prefetch_extension_data(display.xcb, &xcb_xkb_id); xkb_cookie = xcb_xkb_use_extension(display.xcb, XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION); device_cookie = xcb_xkb_get_device_info(display.xcb, XCB_XKB_ID_USE_CORE_KBD, 0, 0, 0, 0, 0, 0); extension_reply = xcb_get_extension_data(display.xcb, &xcb_xkb_id); display.xkb_base_event = extension_reply->first_event; display.xkb_base_error = extension_reply->first_error; xkb_reply = xcb_xkb_use_extension_reply(display.xcb, xkb_cookie, &error); free(xkb_reply); device_reply = xcb_xkb_get_device_info_reply(display.xcb, device_cookie, NULL); display.keyboard_device_id = device_reply->deviceID; free(device_reply); xcb_xkb_select_events(display.xcb, display.keyboard_device_id, ~0, 0, ~0, ~0, ~0, NULL); } static void handle_xkb_event(xcb_generic_event_t *generic_event) { switch (((xcb_xkb_new_keyboard_notify_event_t*) generic_event)->xkbType) { default: printf("xkb: %u\n",((xcb_xkb_new_keyboard_notify_event_t*) generic_event)->xkbType); break; case XCB_XKB_NEW_KEYBOARD_NOTIFY: { xcb_xkb_new_keyboard_notify_event_t *event; event = (xcb_xkb_new_keyboard_notify_event_t*) generic_event; if (event->oldDeviceID == display.keyboard_device_id) { printf("xkb: NEW_KEYBOARD_NOTIFY\n"); display.keyboard_device_id = event->deviceID; } break; } case XCB_XKB_MAP_NOTIFY: { xcb_xkb_map_notify_event_t *event; event = (xcb_xkb_map_notify_event_t*) generic_event; printf("xkb: MAP_NOTIFY\n"); break; } case XCB_XKB_STATE_NOTIFY: { xcb_xkb_state_notify_event_t *event; event = (xcb_xkb_state_notify_event_t*) generic_event; printf("xkb: STATE_NOTIFY\n"); break; } case XCB_XKB_INDICATOR_STATE_NOTIFY: { xcb_xkb_indicator_state_notify_event_t *event; event = (xcb_xkb_indicator_state_notify_event_t*) generic_event; printf("xkb: INDICATOR_STATE_NOTIFY\n"); break; } case XCB_XKB_EXTENSION_DEVICE_NOTIFY: { xcb_xkb_extension_device_notify_event_t *event; event = (xcb_xkb_extension_device_notify_event_t*) generic_event; printf("xkb: EXTENSION_DEVICE_NOTIFY\n"); break; } } } int main(void) { xcb_generic_event_t *event; open_display(); xcb_flush(display.xcb); while (event = xcb_wait_for_event(display.xcb), event != NULL) { if (event->response_type == display.xkb_base_event) { handle_xkb_event(event); } free(event); } return 0; }
问题分析与解答
为什么没有收到NewKeyboardNotify事件?
NewKeyboardNotify的触发条件是整个键盘设备的XKB配置被完全替换,比如单独执行setxkbmap -layout us这类操作,会重新加载完整的键盘映射和设备配置,此时X服务器会发送该事件。- 而通过
grp:alt_shift_toggle切换布局时,只是切换了当前活跃的布局组(Layout Group)——所有布局组的配置早已在执行setxkbmap -layout us,gr时加载完毕,切换操作只是在已有配置间切换活跃组,并未替换整个键盘设备的XKB配置,因此不会触发NewKeyboardNotify。
如何解读收到的两个事件?
1. IndicatorStateNotify
该事件对应键盘指示灯状态的变化:很多桌面环境会用专用指示灯(或软件模拟指示灯)标记当前布局组,切换布局时XKB会更新对应布局组的指示灯状态,因此触发此事件。可以通过事件结构体的changed和state字段,确认具体是哪个指示灯的状态发生了变更。
2. ExtensionDeviceNotify
这是XKB扩展用于通知设备状态变更的通用事件:切换布局组时,XKB内部会更新设备的活跃布局信息,进而触发该事件。可以通过事件中的deviceID和eventType字段,确认是核心键盘设备的状态变更。
正确检测布局切换的方法
要准确捕捉布局组切换,应该监听XCB_XKB_STATE_NOTIFY事件——该事件会在键盘状态(包括活跃布局组、锁状态等)变更时触发。在事件结构体中,group字段表示当前活跃的布局组索引(从0开始,对应us为0,gr为1),通过检查该字段的变化即可实现布局切换检测。
修改事件处理逻辑,重点处理XCB_XKB_STATE_NOTIFY:
case XCB_XKB_STATE_NOTIFY: { xcb_xkb_state_notify_event_t *event = (xcb_xkb_state_notify_event_t*) generic_event; printf("xkb: STATE_NOTIFY - 当前布局组: %u\n", event->group); break; }
内容的提问来源于stack exchange,提问作者cutecoder

