8

I'm working on a window manager, mainly as an exercise, and I'm facing a problem. I would like to be able to raise the clicked window to the top of the stack. Currently, I am using XGrabButton on Button1 and ControlMask to allow for moving of windows, and when I Ctrl+click the window, the desired effect is achieved. However, if I use XGrabButton on Button1 with AnyModifier, while the effect I am looking for is achieved, I can no longer interact with the client window (highlighting text, etc.) through the mouse button. I have tried Grabbing the button on EnterNotify, then ungrabbing the button as soon as the window is raised, but this appears to have no effect and the window manager acts as though I never grabbed the button at all.

My program is still relatively small, so here is the code:

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

I have also tried to use XSendEvent, but based on the results I have gotten from it I don't think I understand what it is supposed to do very well. I am very new to Xlib programming so any help is greatly appreciated.

Thank you!

EthanS
  • 115
  • 7
  • Don't grab a button you want client windows to use (like any button with AnyModifier). Use XSelectInput to express interest in ButtonPressMask | ButtonReleaseMask for each top level window, then process ButtonPress normally. Both the client window and the WM will get the event. – n. m. could be an AI Sep 19 '17 at 10:00
  • As it currently stands, I am actually doing that. On a map request for a window, the window managers processes the request, and sets XSelectInput including the few KeyMasks, and all the ButtonMasks, as you can see in `ewm_on_map_request`. While the KeyPress/KeyRelease events _are_ reported to the window manager, the ButtonEvents don't appear to be **unless** I grab the button beforehand. In the Xlib docs I read somewhere that only one client can SelectInput on Button events at one time, but I know there must be a way to do it since almost all window managers have this feature. Thank you! – EthanS Sep 19 '17 at 13:31
  • Oh sorry yes, you are right. "Only one client at a time can select a ButtonPress event, which is associated with the event mask ButtonPressMask". I always forget about it. – n. m. could be an AI Sep 19 '17 at 14:41
  • 4
    I have looked at a couple of WM sources. Apparently they use XGrabButton, and after processing the event, call XAllowEvents with the ReplayPointer mode. – n. m. could be an AI Sep 19 '17 at 20:52

1 Answers1

3

I had exactly the same problem. The comments in the original question helped, but they're not quite there yet, they're missing an important detail (point 1 ahead). Ultimately I found the hint here solved it.

  1. Grab the event synchronously (notice GrabModeSync given as pointer_mode)
XGrabButton(dis, FOCUS_BUTTON, AnyModifier, root, False, BUTTONMASK, GrabModeSync, GrabModeAsync, None, None);
  1. Use XAllowEvents and Xsync for the pass-through effect
XAllowEvents(display, ReplayPointer, ev->xbutton.time);
XSync(display, 0);
joao
  • 3,517
  • 1
  • 31
  • 43