Ubuntu 20 LTS下全系统键鼠事件捕获与修改方案问询
全系统级鼠标/键盘事件捕获与修改问题(Ubuntu 20 LTS)
我在Ubuntu 20 LTS下开发全系统级的鼠标加速器,需要捕获并修改鼠标、键盘事件后应用到全系统,优先使用系统默认包。目前尝试了X11和XInput2,但都遇到了问题:
一、X11方案的问题
使用XGrabPointer()和XGrabKeyboard()会直接抓取事件,导致鼠标无法和系统环境正常交互;而且事件捕获仅在终端窗口生效,修改鼠标坐标等操作必须先取消抓取才能生效。
编译命令:
gcc app.c -lX11
代码:
#include <X11/Xlib.h> #include <X11/keysym.h> #include <stdio.h> int main(){ Display *display = XOpenDisplay(NULL); Window root = DefaultRootWindow(display); // Grab the mouse pointer XGrabPointer(display, root, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); XGrabKeyboard(display, root, True, GrabModeAsync, GrabModeAsync, CurrentTime); XEvent event; KeySym key; KeyCode keycode; u_int32_t cntr = 0; while (cntr < 1000) { XNextEvent(display, &event); switch (event.type) { case ButtonPress: keycode = event.xkey.keycode; // Check the mouse button pressed switch (event.xbutton.button) { case Button1: // Left mouse button printf("Left mouse button pressed\n"); XUngrabPointer(display, CurrentTime); break; case Button2: // Middle mouse button printf("Middle mouse button pressed\n"); break; case Button3: // Right mouse button printf("Right mouse button pressed\n"); break; default: printf("Unhandled mouse button: %d\n", event.xbutton.button); break; } break; case KeyPress: keycode = event.xkey.keycode; printf("Key event, keycode: %u\n", keycode); // Check if the pressed key is Escape (ESC) if (keycode == XKeysymToKeycode(display, XK_Escape)){ printf("Closed\n"); // Release the mouse pointer grab and close the display XUngrabPointer(display, CurrentTime); XUngrabKeyboard(display, CurrentTime); XCloseDisplay(display); return 0; } break; default: printf("Unhandled event type: %d\n", event.type); break; } cntr++; } // Release the mouse pointer grab when done XUngrabPointer(display, CurrentTime); XCloseDisplay(display); return 0; }
二、XInput2方案的问题
尝试了XInput2扩展,编译命令gcc move_cursor.c -lX11 -lXi,但事件捕获无法在运行脚本的窗口外生效,单显示器下无法更新状态,多显示器场景直接出错。
代码:
#include <stdio.h> #include <X11/Xlib.h> #include <X11/extensions/XInput2.h> int main(){ Display *display = XOpenDisplay(NULL); if (display == NULL){ fprintf(stderr, "Unable to open display.\n"); return 1; } int opcode, event, error; if (!XQueryExtension(display, "XInputExtension", &opcode, &event, &error)){ fprintf(stderr, "X Input extension not available.\n"); XCloseDisplay(display); return 1; } Window root = RootWindow(display, DefaultScreen(display)); XIEventMask evmask; unsigned char mask[(XI_LASTEVENT + 7) / 8] = {0}; evmask.deviceid = XIAllDevices; evmask.mask_len = sizeof(mask); evmask.mask = mask; XISetMask(mask, XI_Motion); XISelectEvents(display, root, &evmask, 1); XEvent ev; int cntr = 0; while (cntr < 1000){ XNextEvent(display, &ev); if (ev.xcookie.type == GenericEvent && ev.xcookie.extension == opcode){ if (XGetEventData(display, &ev.xcookie)){ if (ev.xcookie.evtype == XI_Motion){ XGetEventData(display, &ev.xcookie); printf("Mouse coordinates (%d): X=%d, Y=%d\n", cntr, ev.xmotion.x, ev.xmotion.y); XFreeEventData(display, &ev.xcookie); } XFreeEventData(display, &ev.xcookie); } } cntr++; } XCloseDisplay(display); return 0; }
疑问与需求
- 如何解决当前X11/XInput2的问题,实现全系统级的事件捕获与修改?
- 是否需要使用其他技术?比如我看到了
fcntl.h,但不确定它的相关性和用法。 - 有没有更详尽的开发指导?
解决方案建议
1. X11方案修正:放弃主动抓取,改用事件监听+重定向
不要使用XGrabPointer/Keyboard阻断原生事件,改用XSelectInput监听根窗口的所有事件,同时用XSendEvent将修改后的事件发送回系统:
- 为根窗口设置
PointerMotionMask、ButtonPressMask等所有需要的事件掩码 - 收到事件后修改坐标或按键信息,再通过
XSendEvent将修改后的事件发送到目标窗口 - 确保
XSendEvent参数设置正确,让系统能正常处理事件
2. XInput2方案修正
你的XInput2代码存在两个核心问题:
- 仅监听了单屏幕的根窗口,多屏幕场景需要遍历所有屏幕设置事件监听
- 重复调用
XGetEventData会导致内存泄漏,需仅调用一次
修改后的核心逻辑:
// 遍历所有屏幕,为每个屏幕的根窗口设置事件监听 int num_screens = ScreenCount(display); for (int i = 0; i < num_screens; i++) { Window root = RootWindow(display, i); XISelectEvents(display, root, &evmask, 1); } // 事件处理逻辑修正 if (ev.xcookie.type == GenericEvent && ev.xcookie.extension == opcode) { if (XGetEventData(display, &ev.xcookie)) { if (ev.xcookie.evtype == XI_Motion) { XIDeviceEvent *dev_event = (XIDeviceEvent*)ev.xcookie.data; // 使用全局坐标dev_event->root_x/root_y处理多屏幕 printf("全局坐标: X=%d, Y=%d\n", dev_event->root_x, dev_event->root_y); } XFreeEventData(display, &ev.xcookie); } }
3. 更优的全系统级技术选择
- libinput:Linux系统默认的输入设备管理库,直接从
/dev/input读取事件,修改后注入系统,支持Xorg和Wayland,无需依赖X11,是全系统输入处理的首选方案。 - uinput:内核提供的虚拟输入设备接口,可创建虚拟鼠标/键盘设备,将修改后的事件写入该设备,系统会视为真实输入。需要root权限,配合
libevdev使用更便捷。
关于fcntl.h:它是文件控制头文件,主要用于设置文件描述符属性(如非阻塞模式),仅在直接操作/dev/input设备时可能用到,本身无法解决输入事件捕获与修改的核心问题。
内容的提问来源于stack exchange,提问作者Dhairya Gupta
相关产品推荐
相关产品推荐

