You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

捕获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`

关键说明

  1. 事件订阅而非抓取:xcb_input_xi_select_events()仅向X服务器注册事件监听,不会拦截或独占设备,其他窗口仍能正常接收鼠标事件。
  2. 设备查找:保留原有的指针设备查找逻辑,确保订阅的是正确的鼠标设备事件。
  3. 事件解析:使用XCB_EVENT_RESPONSE_TYPE()宏获取扩展事件的实际类型,确保正确识别Enter/Leave事件。

内容的提问来源于stack exchange,提问作者svlasov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 02:50:27