Frame input shape goes stale when the frame is resized: clicks fall through to the window below
xfwm4 version: 4.20.0 (Manjaro/Arch package 4.20.0-2), X11, compositor enabled. The relevant code is unchanged in current master (frameSetShapeInput() is only called from handleShape() in events.c).
Summary
xfwm4 computes the input shape of the frame window in frameSetShapeInput() (src/frame.c), but that function is only called from handleShape() in src/events.c, i.e. when the client emits a ShapeNotify of kind ShapeInput. If the frame is resized afterwards and the client does not touch its input shape again, the frame keeps the old input region. Pointer events outside that region fall through to whatever window is underneath (the desktop, another application), so parts of the window look fine but cannot be clicked.
How I hit it
Two monitors: laptop eDP 1366x768 (primary, at +0+672) and HDMI 2560x1440 (at +1366+0). Google Chrome (custom frame, it manages its own input shape) maximized on the laptop screen, then moved to the HDMI screen with the move_window_to_monitor_right_key shortcut. The window is resized to 2560x1440 and drawn correctly, but only the top-left 1398x783 pixels (the size of the frame when Chrome last changed its input shape) accept clicks. The tab strip beyond that width and the whole lower/right part of the page are dead; XTranslateCoordinates from the root at those points returns the xfdesktop frame.
Diagnostics (frame window is the parent of the client):
$ xshape 0xacc804 # xfwm4 frame of the Chrome window
bounding: 1 rect(s)
2560x1440+0+0
input: 1 rect(s)
1398x783+0+0 # stale: computed when the frame was on the small monitorForcing the client to re-set its input shape (any XShapeCombineRectangles(..., ShapeInput, ..., ShapeSet, ...) on the client window) makes xfwm4 recompute the frame input shape and the window becomes clickable again, which confirms that the geometry used by frameSetShapeInput() is fine, it is just never re-run.
Minimal reproduction (single monitor is enough)
Attached shape-client.c: a bare Xlib client that maps a 500x300 window, waits one second (so xfwm4 has reparented it and selects ShapeNotify), sets its input shape once to its full rectangle, and prints ButtonPress events.
gcc -o shape-client shape-client.c -lX11 -lXext && ./shape-client- Maximize the window (keyboard shortcut, title bar button, or
wmctrl -i -r <id> -b add,maximized_vert,maximized_horz). - Click anywhere outside the original 500x300 area: the click is not delivered to the client, it goes to the window below. Reading the frame shape shows the input region still being ~504x326 while the frame is 1366x741.
Any client that sets an input shape only on specific state changes (Chromium/Chrome does this for its custom frame) hits the same problem whenever xfwm4 resizes the frame on its own: move to another monitor while maximized, RandR layout changes, etc.
Proposed fix
Recompute the input shape at the end of frameSetShape(), which already runs on every frame redraw/resize. Patch attached as 0001-frame-recompute-input-shape-on-resize.patch:
--- a/src/frame.c
+++ b/src/frame.c
@@ -736,6 +736,11 @@
XShapeCombineShape (display_info->dpy, c->frame, ShapeBounding, 0, 0, screen_info->shape_win, ShapeBounding, ShapeSet);
myDisplayErrorTrapPopIgnored (display_info);
+
+ /* The frame may have been resized without the client touching its input
+ * shape (e.g. a maximized window moved to another monitor), so recompute
+ * the frame input shape from the current geometry. */
+ frameSetShapeInput (c);
}For clients that never set an input shape, frameSetShapeInput() yields the frame bounding shape, i.e. the default, so the change is a no-op for them.
Tested on 4.20.0 with the patch applied (xfwm4 --replace with the patched binary): the minimal client gets a full-size input region after being maximized, and the maximized Chrome window keeps a matching input region after being moved between monitors with the keyboard shortcuts.
shape-client.c (same as the attachment)
// Minimal X11 client that sets its input shape exactly once (to its full rectangle)
// and reports button presses. Used to reproduce xfwm4 keeping a stale frame input
// shape after the frame is resized without a new ShapeNotify from the client.
// Build: gcc -o shape-client shape-client.c -lX11 -lXext
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/shape.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
Display *d = XOpenDisplay(NULL);
if (!d) { fprintf(stderr, "cannot open display\n"); return 1; }
int s = DefaultScreen(d);
Window w = XCreateSimpleWindow(d, RootWindow(d, s), 100, 100, 500, 300, 0,
BlackPixel(d, s), WhitePixel(d, s));
XStoreName(d, w, "shape-client");
XSelectInput(d, w, ButtonPressMask | StructureNotifyMask);
XMapWindow(d, w);
XFlush(d);
printf("window 0x%lx\n", w);
fflush(stdout);
/* Set the input shape once, after the window manager has reparented us,
* so that xfwm4 receives a ShapeNotify(ShapeInput) and shapes the frame. */
sleep(1);
XRectangle r = {0, 0, 500, 300};
XShapeCombineRectangles(d, w, ShapeInput, 0, 0, &r, 1, ShapeSet, Unsorted);
XFlush(d);
printf("input shape set to 500x300\n");
fflush(stdout);
for (;;) {
XEvent e;
XNextEvent(d, &e);
if (e.type == ButtonPress) {
printf("click at %d,%d\n", e.xbutton.x, e.xbutton.y);
fflush(stdout);
} else if (e.type == ConfigureNotify) {
printf("configured %dx%d\n", e.xconfigure.width, e.xconfigure.height);
fflush(stdout);
}
}
}