c - 如何在 X11 上监视所有 Windows(不仅仅是一个)中的鼠标移动事件

标签 c linux user-interface x11

我正在尝试编写一个 X11 程序来监视桌面上的所有鼠标移动。每当人类用户移动鼠标或通过 XWarpPointer() 以编程方式移动鼠标时,程序应该能够接收到通知。通过机器人应用程序。我知道通过设置 PointerMotionMask 应该是可能的通过 XSelectInput()和监控MotionNotify ,但我无法从所有窗口接收鼠标事件,而不仅仅是一个。

最初,在下面的演示中,我只是尝试从根窗口接收指针运动事件。

#include <stdio.h>
#include <X11/Xlib.h>

int main(int argc, char **argv)
{
    Display *display;
    Window root_window;
    XEvent event;

    display = XOpenDisplay(0);
    root_window = XRootWindow(display, 0); 
    XSelectInput(display, root_window, PointerMotionMask);

    while (1) {
        XNextEvent(display, &event);
        switch(event.type) { 
            case MotionNotify:
                printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y );
                break;
        }
    }   
    return 0;
}

但它不会接收任何事件,除非鼠标指针位于空的桌面背景上。很明显,仅仅从根窗口接收事件是行不通的。然后我尝试了一个解决方法:首先,设置 SubstructureNotifyMask在根窗口监控所有CreateNotify事件捕获所有新创建的窗口,然后调用 XSelectInput()启用 PointerMotionMask在这些 window 上。
#include <stdio.h>
#include <X11/Xlib.h>

int main(int argc, char **argv)
{
    Display *display;
    Window root_window;
    XEvent event;

    display = XOpenDisplay(0);
    root_window = XRootWindow(display, 0); 
    XSelectInput(display, root_window, SubstructureNotifyMask);

    while (1) {
        XNextEvent(display, &event);
        switch(event.type) { 
            case CreateNotify:
                XSelectInput(display, event.xcreatewindow.window, PointerMotionMask);
                break;
            case MotionNotify:
                printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y);
                break;
        }
    }   
    return 0;
}

这种方法比较成功,我开始从新窗口接收一些鼠标事件。不幸的是,它仍然不能在窗口内的所有部分工作 - 例如,它不能从终端模拟器的控制台区域接收鼠标事件,但可以在鼠标位于标题栏周围时接收事件。似乎一个窗口可以创建更多的子窗口,因此不会记录鼠标事件。

然后我尝试了另一种解决方法 - 设置 SubstructureNotifyMaskPointerMotionMaskCreateNotify ,所以当一个窗口创建一个子窗口时,SubstructureNotifyMask确保更多 CreateNotify事件将以递归方式接收,因此所有子窗口都将获得 PointerMotionMask也是。
#include <stdio.h>
#include <X11/Xlib.h>

int main(int argc, char **argv)
{
    Display *display;
    Window root_window;
    XEvent event;

    display = XOpenDisplay(0);
    root_window = XRootWindow(display, 0); 
    XSelectInput(display, root_window, SubstructureNotifyMask);

    while (1) {
        XNextEvent(display, &event);
        switch(event.type) { 
            case CreateNotify:
                XSelectInput(display, event.xcreatewindow.window, SubstructureNotifyMask | PointerMotionMask);
                break;
            case MotionNotify:
                printf("x: %d y: %d\n", event.xmotion.x, event.xmotion.y);
                break;
        }
    }   
    return 0;
}

它比第二个例子好一点,但它不可靠:
  • X 是完全异步的,是否有可能在我们有机会 XSelectInput() 之前创建了子窗口? ?
  • 有时它只报告 BadWindow错误和崩溃。
  • X 事件处理变得困惑 - 如果程序已经处理了很多不同的 X 事件,启用 SubstructureNotifyMask递归将使许多不相关的事件传递给其他处理程序,并且添加额外的代码来区分想要的和不需要的事件是很痛苦的。


  • 那么,如何在 X11 上监控所有窗口中的鼠标移动事件呢?

    最佳答案

    在做了一些研究之后,尤其是阅读了 Xeyes 的源代码(我总是觉得这个演示很愚蠢,但在这里很有帮助!),我发现:

  • 调用XSelectInput()在所有窗口和子窗口上都是徒劳的尝试,您必须在曾经创建的每个窗口和子窗口上设置一个掩码,这不是一个强大的解决方案,也不推荐。
  • 相反,最好通过 XQueryPointer() 显式地从 X 服务器中不断地拉动鼠标指针。 ,而不是要求 X 服务器将 MotionEvent 推送给我们。

  • 一个简单的解决方案是通过 XtAppAddTimeOut() 设置一个计时器。并调用 XQueryPointer()周期性地,它起作用,而且确实,it was what Xeyes did in the past !但它不必要地浪费了 CPU 时间。如今,最佳实践是利用 XInputExtention 2.0。工作流程是:
  • 初始化 XInput v2.0
  • 通过 XISetMask() 启用各种掩码和 XIEventMask()接收XI_RawMotion来自 XI_Motion 的事件(或 XIAllMasterDevices,见下面的注释) (或 XIAllDevices )。
  • 当一个 XI_RawMotion (或 XI_Motion )事件已收到,请调用 XQueryPointer() .
  • XQueryPointer()返回:
  • 鼠标相对于根窗口的坐标。
  • 鼠标光标下的事件窗口(如果有)。

  • 执行 XTranslateCoordinates()如果我们想要相对于鼠标光标下的事件窗口的相对坐标。

  • 演示
    这是一个演示(另存为 mouse.c ,用 gcc mouse.c -o mouse -lX11 -lXi 编译)。但是,它无法检测到 XWarpPointer() ,请参阅下面的注释。
    #include <stdio.h>
    #include <assert.h>
    #include <X11/Xlib.h>
    #include <X11/extensions/XInput2.h>
    
    int main(int argc, char **argv)
    {
        Display *display;
        Window root_window;
    
        /* Initialize (FIXME: no error checking). */
        display = XOpenDisplay(0);
        root_window = XRootWindow(display, 0);
    
        /* check XInput */
        int xi_opcode, event, error;
        if (!XQueryExtension(display, "XInputExtension", &xi_opcode, &event, &error)) {
            fprintf(stderr, "Error: XInput extension is not supported!\n");
            return 1;
        }
    
        /* Check XInput 2.0 */
        int major = 2;
        int minor = 0;
        int retval = XIQueryVersion(display, &major, &minor);
        if (retval != Success) {
            fprintf(stderr, "Error: XInput 2.0 is not supported (ancient X11?)\n");
            return 1;
        }
    
        /*
         * Set mask to receive XI_RawMotion events. Because it's raw,
         * XWarpPointer() events are not included, you can use XI_Motion
         * instead.
         */
        unsigned char mask_bytes[(XI_LASTEVENT + 7) / 8] = {0};  /* must be zeroed! */
        XISetMask(mask_bytes, XI_RawMotion);
    
        /* Set mask to receive events from all master devices */
        XIEventMask evmasks[1];
        /* You can use XIAllDevices for XWarpPointer() */
        evmasks[0].deviceid = XIAllMasterDevices;
        evmasks[0].mask_len = sizeof(mask_bytes);
        evmasks[0].mask = mask_bytes;
        XISelectEvents(display, root_window, evmasks, 1);
    
        XEvent xevent;
        while (1) {
            XNextEvent(display, &xevent);
    
            if (xevent.xcookie.type != GenericEvent || xevent.xcookie.extension != xi_opcode) {
                /* not an XInput event */
                continue;
            }
            XGetEventData(display, &xevent.xcookie);
            if (xevent.xcookie.evtype != XI_RawMotion) {
                /*
                 * Not an XI_RawMotion event (you may want to detect
                 * XI_Motion as well, see comments above).
                 */
                XFreeEventData(display, &xevent.xcookie);
                continue;
            }
            XFreeEventData(display, &xevent.xcookie);
    
            Window root_return, child_return;
            int root_x_return, root_y_return;
            int win_x_return, win_y_return;
            unsigned int mask_return;
            /*
             * We need:
             *     child_return - the active window under the cursor
             *     win_{x,y}_return - pointer coordinate with respect to root window
             */
            int retval = XQueryPointer(display, root_window, &root_return, &child_return,
                                       &root_x_return, &root_y_return,
                                       &win_x_return, &win_y_return,
                                       &mask_return);
            if (!retval) {
                /* pointer is not in the same screen, ignore */
                continue;
            }
    
            /* We used root window as its reference, so both should be the same */
            assert(root_x_return == win_x_return);
            assert(root_y_return == win_y_return);
    
            printf("root: x %d y %d\n", root_x_return, root_y_return);
    
            if (child_return) {
                int local_x, local_y;
                XTranslateCoordinates(display, root_window, child_return,
                                      root_x_return, root_y_return,
                                      &local_x, &local_y, &child_return);
                printf("local: x %d y %d\n\n", local_x, local_y);
            }
        }
    
        XCloseDisplay(display);
    
        return 0;
    }
    
    样本输出
    root: x 631 y 334
    local: x 140 y 251
    
    root: x 628 y 338
    local: x 137 y 255
    
    root: x 619 y 343
    local: x 128 y 260
    
    XWarpPointer()麻烦事
    如果指针通过 XWarpPointer() 移动,则上面的演示不起作用通过 X.Org 1.10.4 之后的较新系统上的机器人应用程序。这是故意的,见Bug 30068在 FreeDesktop 上。
    为了接收所有鼠标移动触发的鼠标事件,包括XWarpPointer() , 更改 XI_RawMotionXI_Motion , 并更改 XIAllMasterDevicesXIAllDevices .
    引用
    此演示缺少错误检查,并且可能包含错误。如有疑问,请查阅以下权威引用资料。
  • Tracking Cursor PositionKeith Packard ,真正的 X 专家,自 1980 年代后期以来一直大量参与 X 的开发,并负责许多 X 扩展和技术论文。
  • Xeyes source code来自 X.Org。
  • 关于c - 如何在 X11 上监视所有 Windows(不仅仅是一个)中的鼠标移动事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62448181/

    相关文章:

    linux - 错误共享库 libopencv_xphoto.so.3.1 丢失

    user-interface - 如何在 Lua 中制作 GUI 应用程序

    user-interface - 如何在 WEKA GUI 中更改名义属性值顺序?

    c++ - 带隐藏和 WA_QuitOnClose 的 Qt QDialog

    c++ - SSL_CTX_set_cipher_list() 没有影响

    c - Fork 永远不会进入子进程

    linux - 如何在 bash 脚本中循环遍历子目录?

    c - C 无法显示文件内容

    c - 你如何在函数内部使用 struct 的 malloc?

    linux - 是否可以允许用户访问目录而不使他成为所有者?