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

为何这段X11代码无法捕获鼠标ButtonPress事件?

问题描述

在大型应用的取色器功能中,我使用XGrabPointer捕获指针事件,已设置掩码为ButtonPressMask,但客户端始终收不到ButtonPress事件。

点击应用按钮触发BlockAndReadMouse函数后,能收到LeaveNotify(事件类型8)和PropertyNotify(事件类型28),但完全无法获取ButtonPress事件。鼠标操作确实被阻断了,点击、滚动等功能全部失效,可目标事件就是没有上报。我写了个逻辑类似的简单测试程序能正常运行,不清楚当前代码遗漏了什么。

相关代码

void ScreenUtilitiesLinux::xEventHandler(void* eventPtr) {
    X11::XEvent* event = (X11::XEvent*) eventPtr;

    LOG(Warning, "Got event. {0}", event->type);
    X11::Display* display = X11::XOpenDisplay(NULL);

    if (event->type == ButtonPress) {
        LOG(Warning, "Got MOUSE CLICK event.");
        LinuxPlatform::xEventRecieved.Unbind(xEventHandler); // 解绑事件,只处理一次点击

        X11::XUngrabPointer(display, CurrentTime);
        X11::XCloseDisplay(display);
    } else 
    {
        LOG(Warning, "Got a different event..");
        X11::XCloseDisplay(display);
    }
}

void ScreenUtilitiesLinux::BlockAndReadMouse()
{
    X11::Display* display = X11::XOpenDisplay(NULL);
    X11::Window rootWindow = X11::XRootWindow(display, X11::XDefaultScreen(display));

    X11::Cursor cursor = XCreateFontCursor(display, 130);
    int grabbedPointer = X11::XGrabPointer(display, rootWindow, 0,  ButtonPressMask, GrabModeAsync, GrabModeAsync, rootWindow, cursor, CurrentTime);
    if (grabbedPointer != GrabSuccess) {
        LOG(Error, "Failed to grab cursor for events.");
        return;
    }

    LinuxPlatform::xEventRecieved.Bind(xEventHandler);
}

问题排查与修复方案

1. 重复打开/关闭Display的致命错误

xEventHandler中每次都调用XOpenDisplay(NULL)和XCloseDisplay(display)是核心问题:

  • 新打开的Display和BlockAndReadMouse中用于抓指针的Display不是同一个上下文,导致XUngrabPointer无法生效,还会干扰事件分发
  • 频繁创建销毁Display连接会引发资源泄漏,破坏X11事件循环的稳定性

修复: 将Display实例存在类成员变量中,或者通过参数传递给事件处理函数,避免重复打开关闭。例如修改xEventHandler的参数,传入已打开的Display指针:

void ScreenUtilitiesLinux::xEventHandler(X11::Display* display, void* eventPtr) {
    X11::XEvent* event = (X11::XEvent*) eventPtr;

    LOG(Warning, "Got event. {0}", event->type);

    if (event->type == ButtonPress) {
        LOG(Warning, "Got MOUSE CLICK event.");
        LinuxPlatform::xEventRecieved.Unbind(std::bind(&ScreenUtilitiesLinux::xEventHandler, this, display, std::placeholders::_1));

        X11::XUngrabPointer(display, CurrentTime);
        X11::XFreeCursor(display, cursor); // 顺便释放cursor资源
        X11::XCloseDisplay(display);
    } else 
    {
        LOG(Warning, "Got a different event..");
    }
}

2. 抓取参数owner_events设置错误

XGrabPointer的第三个参数owner_events设为0时,只有抓取窗口(这里是root窗口)的事件会被发送到客户端,但大型应用的事件循环可能过滤了root窗口的事件。

修复: 将owner_events改为1,这样即使事件原本属于其他窗口,也会被转发到当前客户端:

int grabbedPointer = X11::XGrabPointer(display, rootWindow, 1,  ButtonPressMask, GrabModeAsync, GrabModeAsync, rootWindow, cursor, CurrentTime);

3. 应用事件转发机制的过滤问题

LinuxPlatform::xEventRecieved可能只转发应用自身窗口的事件,而被抓取的ButtonPress事件会发送到root窗口,因此被过滤掉。

验证方法: 跳过应用的事件转发机制,直接在BlockAndReadMouse中用XNextEvent等待事件:

void ScreenUtilitiesLinux::BlockAndReadMouse()
{
    X11::Display* display = X11::XOpenDisplay(NULL);
    X11::Window rootWindow = X11::XRootWindow(display, X11::XDefaultScreen(display));

    X11::Cursor cursor = XCreateFontCursor(display, 130);
    int grabbedPointer = X11::XGrabPointer(display, rootWindow, 1,  ButtonPressMask, GrabModeAsync, GrabModeAsync, rootWindow, cursor, CurrentTime);
    if (grabbedPointer != GrabSuccess) {
        LOG(Error, "Failed to grab cursor for events.");
        XCloseDisplay(display);
        return;
    }

    X11::XEvent event;
    while (true) {
        XNextEvent(display, &event);
        if (event.type == ButtonPress) {
            LOG(Warning, "Got MOUSE CLICK event.");
            break;
        }
    }

    X11::XUngrabPointer(display, CurrentTime);
    X11::XFreeCursor(display, cursor);
    XCloseDisplay(display);
}

如果这个测试能收到ButtonPress事件,说明问题出在LinuxPlatform::xEventRecieved的过滤逻辑上,需要调整规则,允许转发root窗口的ButtonPress事件。

4. Cursor资源泄漏

XCreateFontCursor创建的光标没有被销毁,需要在XUngrabPointer后调用XFreeCursor(display, cursor)释放资源。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 20:12:48