在Xlib中捕获按钮事件,然后将事件传递给客户端

8
我正在开发一个窗口管理器,主要是为了练习,并且我遇到了一个问题。我希望能够将点击的窗口置于堆栈的顶部。目前,我使用XGrabButton在Button1上和ControlMask一起移动窗口,并且当我Ctrl +单击窗口时,可以实现所需的效果。然而,如果我在Button1上使用XGrabButton并使用AnyModifier,虽然我想要的效果已经实现,但我无法通过鼠标按钮与客户端窗口进行交互(突出显示文本等)。我尝试在EnterNotify上捕获按钮,然后在窗口被提升后立即取消捕获该按钮,但这似乎没有任何效果,窗口管理器的行为就像我根本没有捕获该按钮一样。
我的程序仍然相对较小,所以这里是代码:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "window_manager.h"


ewm_instance wm;

void 
ewm_init()
{
    wm._display = XOpenDisplay(NULL);

    if (!wm._display) {
        printf("Could not open display %s\n", XDisplayName(NULL));
    } 
    wm._root = DefaultRootWindow(wm._display);
}

void
ewm_run()
{
    XSelectInput(wm._display, 
                 wm._root, 
                 SubstructureRedirectMask | SubstructureNotifyMask | 
                 KeyPressMask | KeyReleaseMask | 
                 ButtonPressMask | ButtonReleaseMask);

    XSync(wm._display, 0);
    XGrabServer(wm._display);
    Window returned_root, returned_parent;
    Window *top_level_windows;
    unsigned int num_top_level_windows;
    XQueryTree(wm._display,
               wm._root,
               &returned_root,
               &returned_parent,
               &top_level_windows,
               &num_top_level_windows);
    XFree(top_level_windows);
    XUngrabServer(wm._display);
    XGrabButton(
        wm._display,
        Button1,
        ControlMask,
        wm._root,
        0,
        ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
        GrabModeAsync,
        GrabModeAsync,
        None,
        None);

    XGrabButton(
        wm._display,
        Button1,
        ControlMask,
        wm._root,
        0,
        ButtonPressMask | ButtonReleaseMask | ButtonMotionMask,
        GrabModeAsync,
        GrabModeAsync,
        None,
        None);

    for (;;) {
        XEvent e;
        XNextEvent(wm._display, &e);

        switch (e.type) {
        case CreateNotify:
            printf("CreateNotify\n");
            break;
        case DestroyNotify:
            printf("DestroyNotify\n");
            break;
        case ReparentNotify:
            printf("ReparentNotify\n");
            break;
        case MapNotify:
            printf("Mapping Window\n");
            break;
        case UnmapNotify:
            printf("UnmapNotify\n");
            break;
        case ConfigureNotify:
            printf("ConfigureNotify\n");
            break;
        case MapRequest:
            printf("MapRequest\n");
            ewm_on_map_request(&e.xmaprequest);
            break;
        case ConfigureRequest:
            printf("ConfigureRequest\n");
            break;
        case ButtonPress:
            printf("ButtonPress\n");
            ewm_on_button_press(&e.xbutton);
            break;
        case ButtonRelease:
            printf("ButtonRelease\n");
            break;
        case MotionNotify:
            ewm_on_motion_notify(&e.xmotion);
            break;
        case KeyPress:
            printf("KeyPress\n");
            ewm_on_key_press(&e.xkey);
            break;
        case KeyRelease:
            printf("KeyRelease\n");
            break;
        case EnterNotify:
            ewm_on_enter_notify(&e.xcrossing);
            break;
        default:
            printf("Something else\n");
        }
    }
}

void
ewm_on_map_request(const XMapRequestEvent *e)
{
    XSelectInput(
            wm._display,
            e->window,
            KeyPressMask | KeyReleaseMask |
            EnterWindowMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask);

    XMapWindow(wm._display, e->window);
    XSetInputFocus(wm._display, e->window, RevertToPointerRoot, CurrentTime);

}

void
ewm_on_enter_notify(const XEnterWindowEvent *e)
{
    printf("Entered window: %lu\n", e->window);
    XSetInputFocus(wm._display, e->window, RevertToParent, CurrentTime);
}


void
ewm_on_key_press(const XKeyEvent *e)
{
    if ((e->state & ControlMask) && 
         e->keycode == XKeysymToKeycode(wm._display, XK_q)) {
        printf("Destroying window\n");
        XDestroyWindow(wm._display, e->window);
    }

    if ((e->state & ControlMask) && 
         e->keycode == XKeysymToKeycode(wm._display, XK_Return)) {
        printf("Enter Works\n");
        system("urxvt &");

    }
}


void
ewm_on_button_press(const XButtonEvent *e)
{
    if (e->subwindow != 0) {

        // Save initial cursor position;
        wm._cursor_start_position = (Vector){e->x_root, e->y_root};

        // Save initial window info
        Window returned_root;
        int x, y;
        unsigned int width, height, depth, border_width;
        XGetGeometry(wm._display,
                     e->subwindow,
                     &returned_root,
                     &x, &y, 
                     &width, &height,
                     &border_width,
                     &depth);
        wm._window_start_position = (Vector){x, y};
        wm._window_start_size = (Size){width, height};

        XRaiseWindow(wm._display, e->subwindow);
        XSetInputFocus(wm._display, e->subwindow, RevertToParent, CurrentTime);
        printf("Raising window %lu\n", e->subwindow);
        printf("root id: %lu\n", wm._root);
        XUngrabButton(wm._display, Button1, AnyModifier, e->subwindow);
    }
}

void
ewm_on_motion_notify(const XMotionEvent *e)
{
    const Vector drag_pos = {e->x_root, e->y_root};
    const Vector delta = {
        (drag_pos.x - wm._cursor_start_position.x), 
        (drag_pos.y - wm._cursor_start_position.y)
    };

    if ((e->state & Button1Mask) && (e->state & ControlMask)) {
        const Vector dest_window_pos = {
            (wm._window_start_position.x + delta.x), 
            (wm._window_start_position.y + delta.y)
        };

        if (e->subwindow != 0) {
            XMoveWindow(wm._display, 
                    e->subwindow, 
                    dest_window_pos.x, 
                    dest_window_pos.y);
        }
    }
}

void 
ewm_cleanup()
{
    XCloseDisplay(wm._display);
}

我也尝试使用XSendEvent,但根据我的结果,我认为我对它应该做什么并不是很了解。我非常新手Xlib编程,所以非常感谢任何帮助。

谢谢!


不要占用您想让客户端窗口使用的按钮(例如带有AnyModifier的任何按钮)。使用XSelectInput表达对每个顶级窗口的ButtonPressMask | ButtonReleaseMask的兴趣,然后正常处理ButtonPress。客户端窗口和WM都将接收到该事件。 - n. m.
目前情况是,我正在实际执行这个操作。在窗口的地图请求中,窗口管理器处理该请求,并设置XSelectInput,包括少量的KeyMasks和所有的ButtonMasks,正如您在ewm_on_map_request中所看到的那样。虽然KeyPress/KeyRelease事件会被报告给窗口管理器,但是ButtonEvents似乎不会出现,除非我事先抓住了按钮。在Xlib文档中,我读到过只有一个客户端可以同时选择Button事件的输入,但我知道几乎所有的窗口管理器都有这个功能,所以肯定有一种方法可以做到。谢谢! - EthanS
哦,对不起,是的,你说得对。 “一次只能有一个客户端选择与事件掩码ButtonPressMask相关联的ButtonPress事件”。 我总是忘记这个。 - n. m.
4
我查看了一些 WM 的资料。显然它们使用 XGrabButton,在处理事件后,以“ReplayPointer”模式调用 XAllowEvents。 - n. m.
1个回答

3

我曾经遇到完全相同的问题。原始问题中的评论有所帮助,但它们还不够完整,缺少一个重要细节(请看下面的第1点)。最终,我在这里找到了提示解决了它。

  1. 同步地抓取事件(注意将pointer_mode设置为GrabModeSync
XGrabButton(dis, FOCUS_BUTTON, AnyModifier, root, False, BUTTONMASK, GrabModeSync, GrabModeAsync, None, None);
  1. 使用XAllowEventsXsync实现穿透效果
XAllowEvents(display, ReplayPointer, ev->xbutton.time);
XSync(display, 0);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接