捕获X11鼠标enter/leave事件时事件穿透失效的问题求助
捕获全局鼠标Enter/Leave事件且不拦截正常输入(XCB)
问题描述
需要在鼠标进入或离开任意窗口时收到通知,最初通过xcb_input_xi_grab_device()在根窗口实现事件捕获,虽能收到Enter/Leave事件,但出现以下问题:
- 鼠标指针消失
- 所有鼠标事件被拦截,根窗口的子窗口无法响应点击
- 即使设置
XCB_INPUT_GRAB_OWNER_OWNER(预期允许事件穿透)也无效
尝试过xcb_grab_button()、xcb_input_xi_select_events()、xcb_input_xi_passive_grab_device()、xcb_grab_pointer()等函数,要么无法收到目标事件,要么依旧拦截正常输入。
原因分析
xcb_input_xi_grab_device()属于主动设备抓取,其设计目的是独占设备(如拖拽、游戏场景),无论如何设置参数,都会接管设备的事件流,导致其他窗口无法收到鼠标事件。同时传入XCB_CURSOR_NONE会让X服务器停止渲染鼠标指针,造成指针消失的问题。
正确的思路是订阅事件而非抓取设备:通过xcb_input_xi_select_events()在根窗口上注册Enter/Leave事件监听,让X服务器将这些事件转发给客户端,同时不影响其他窗口的正常事件处理。
解决方案
使用xcb_input_xi_select_events()替代主动设备抓取,在根窗口上订阅目标设备的Enter/Leave事件,无需独占设备即可收到全局的窗口进入/离开通知。
修改后的代码
#include <stdio.h> #include <xcb/xcb.h> #include <xcb/xinput.h> #include <stdint.h> xcb_input_device_id_t find_device(xcb_connection_t *conn) { xcb_input_xi_query_device_reply_t *reply = xcb_input_xi_query_device_reply( conn, xcb_input_xi_query_device(conn, XCB_INPUT_DEVICE_ALL), NULL); xcb_input_xi_device_info_iterator_t iter = xcb_input_xi_query_device_infos_iterator(reply); xcb_input_device_id_t device_id = 0; int found = 0; while (iter.rem && !found) { xcb_input_xi_device_info_t *device = iter.data; switch (device->type) { case XCB_INPUT_DEVICE_TYPE_MASTER_POINTER: case XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER: case XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE: device_id = device->deviceid; found = 1; break; } xcb_input_xi_device_info_next(&iter); } free(reply); return device_id; } int main() { xcb_connection_t *conn = xcb_connect(NULL, NULL); if (xcb_connection_has_error(conn)) { printf("Failed to connect to X server\n"); return -1; } xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; xcb_input_device_id_t device_id = find_device(conn); if (device_id == 0) { printf("Failed to find pointer device\n"); xcb_disconnect(conn); return -1; } // 订阅Enter/Leave事件 uint32_t event_mask = XCB_INPUT_XI_EVENT_MASK_ENTER | XCB_INPUT_XI_EVENT_MASK_LEAVE; xcb_input_xi_select_events_info_t select_info = { .deviceid = device_id, .mask_len = sizeof(event_mask) / sizeof(uint32_t), .mask = &event_mask }; xcb_generic_error_t *error = xcb_request_check( conn, xcb_input_xi_select_events(conn, screen->root, 1, &select_info).sequence ); if (error) { printf("Failed to select events: Error code %d\n", error->error_code); free(error); xcb_disconnect(conn); return -1; } xcb_flush(conn); xcb_generic_event_t *event; while ((event = xcb_wait_for_event(conn))) { // 解析XInput扩展事件类型 switch (XCB_EVENT_RESPONSE_TYPE(event)) { case XCB_INPUT_ENTER: printf("Mouse entered a window\n"); break; case XCB_INPUT_LEAVE: printf("Mouse left a window\n"); break; } free(event); } xcb_disconnect(conn); return 0; }
编译命令
cc -O2 app.c -o app `pkg-config --cflags --libs xcb xcb-xinput`
关键说明
- 事件订阅而非抓取:
xcb_input_xi_select_events()仅向X服务器注册事件监听,不会拦截或独占设备,其他窗口仍能正常接收鼠标事件。 - 设备查找:保留原有的指针设备查找逻辑,确保订阅的是正确的鼠标设备事件。
- 事件解析:使用
XCB_EVENT_RESPONSE_TYPE()宏获取扩展事件的实际类型,确保正确识别Enter/Leave事件。
内容的提问来源于stack exchange,提问作者svlasov
相关产品推荐
相关产品推荐

