为何这段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

