Search code examples
c++linuxgnomexlibxorg

Xlib window manager not receiving ButtonPress event for xterm window


I have developed a window manager using Xlib, where I handle ButtonPress events to set the InputFocus on clicked windows. However, when I open an Xterm window within my window manager, I do not receive the ButtonPress event when clicking on the Xterm window. I am unable to set the InputFocus to the Xterm window, actually, I can't receive any event from Xterm window, the only one that is being sent to my event handler is the events from EnterWindowMask.

I suspect that Xterm is intercepting all events that are not propagating to my window manager. But, when running Xterm under the Mutter window manager, Mutter is able to detect the click on the Xterm window and set the input focus to it.

I would like to understand how Mutter achieves this behavior of triggering some event for the Xterm window and successfully setting the input focus to it when I click on that window. What mechanisms does Mutter employ to intercept and handle input events at a higher level, allowing it to manage window focus and handle button presses on application windows? Are there any specific Xlib or X Input Extension functions I should be using in my Xlib window manager to achieve similar functionality?

My code is something similar to this:

#include <X11/Xlib.h>
#include <iostream>
#include <unordered_map>
#include "FrameWindow.h"

Display* display = nullptr;
std::unordered_map<Window, Window> frames;
std::unordered_map<Window, FrameWindow *> framesMap;

void handleButtonPress(const XButtonEvent event)
{
    if (frames.count(event.window)) {
        XSetInputFocus(display, event.window, RevertToPointerRoot, CurrentTime);
    }
  
}

void addWindowFrame(Window window, Bool isPreExisting)
{
  if (isPreExisting) {
    XWindowAttributes winAttrs;
    XGetWindowAttributes(display, window, &winAttrs);
    if (
      winAttrs.override_redirect
      || winAttrs.map_state != IsViewable
    ) {
      return;
    }

    XSetWindowBorderWidth(display, window, 0);
  }

  FrameWindow *frame = new FrameWindow(display, window);
  frames[window] = frame->frameWindow;
  framesMap[frame->frameWindow] = frame;

}

void handleMapRequest(const XMapRequestEvent event)
{
  addWindowFrame(event.window, false);
  XMapWindow(display, event.window);
}

int main() {
    display = XOpenDisplay(NULL);
    if (!display) {
        std::cerr << "Failed to open X display" << std::endl;
        return 1;
    }

    Window rootWindow = DefaultRootWindow(display);

    long events = SubstructureRedirectMask
                | SubstructureNotifyMask
                | StructureNotifyMask
                | ButtonPressMask;
    XSelectInput(display, rootWindow, events);

    XFlush(display);

    XEvent evt;
    while (true) {
        XNextEvent(display, &evt);
        switch (evt.type) {
            case CreateNotify:
                handleCreateNotify(evt.xcreatewindow);
                break;
            case ConfigureRequest:
                handleConfigureRequest(evt.xconfigurerequest);
                break;
            case ConfigureNotify:
                break;
            case MapRequest:
                handleMapRequest(display, evt.xmaprequest);
                break;
            case MapNotify:
                handleMapNotify(evt.xmap);
                break;
            case UnmapNotify:
                handleUnmapNotify(evt.xunmap);
                break;
            case ReparentNotify:
                handleReparentNotify(evt.xreparent);
                break;
            case Expose:
                handleButtonPress(evt.xexpose);
                break;
            case ButtonPress:
                handleButtonPress(evt.xbutton);
                break;
            case ButtonRelease:
                handleButtonRelease(evt.xbutton);
                break;
            case MotionNotify:
                handleMotionNotify(evt.xmotion);
                break;
            case DestroyNotify:
                handleDestroyNotify(evt.xdestroywindow);
                break;
        }
    }

    // Clean up and close the display
    XCloseDisplay(display);

    return 0;
}

Solution

  • I could not debug mutter yet in order to understand how it handles this kind of situation, but to work around this issue and ensure that my window manager receives the ButtonPress event for the xterm window, I've used the XGrabButton with the ButtonPressMask on the specific Xterm window that was framed with my window manager. By doing so, I could override the event handling in the Xterm window and my window manager processes the ButtonPress events.

    Here's an example of how to use XGrabButton on window that has the same issue that xterm:

    void addWindowFrame(Window window, Bool isPreExisting)
    {
      // ...
    
      XGrabButton(
        display, Button1, AnyModifier, contentWindow, true,
        ButtonPressMask, GrabModeAsync, GrabModeAsync, None, None
      );
    
      // ...
    }