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

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;
}

疑问与需求

  1. 如何解决当前X11/XInput2的问题,实现全系统级的事件捕获与修改?
  2. 是否需要使用其他技术?比如我看到了fcntl.h,但不确定它的相关性和用法。
  3. 有没有更详尽的开发指导?

解决方案建议

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 14:58:11