X11 windows now pair their Wayland surface after membership decisions run (membership starts at first map), so instantly-mapped transients -- GTK menu dropdowns -- attached without ever wiring their scene surface: emacs menus did nothing, silently. The associate handlers now replay the map when the surface is already mapped, members join the owner's list before that replay (show_current walks the list to pick visibility), X11 members keep their own geometry instead of being stretched maximized, and late attaches force whole-scene damage so the first frame is never black. Members render through wlr_scene_subsurface_tree_create; the ctl test feed gains CTL_CMD_PTR_BUTTON so tests can click for real; tests/xpopup.c probes the exact menu shape (instantly-mapped managed transient), and a regression test locks in the attach/replay/focus lifecycle.
131 lines
4.6 KiB
C
131 lines
4.6 KiB
C
/* xpopup: an X11 menu-popup probe. Maps a main frame first (like any app),
|
|
* then -- after a configurable delay, mimicking a user opening a menu --
|
|
* creates and instantly maps a second window as a managed transient of the
|
|
* frame, filled with a distinct colour at known coordinates.
|
|
*
|
|
* GTK's X11 menus are regular (non-override-redirect) transients that map
|
|
* immediately on click; this reproduces that shape so a compositor bug in
|
|
* the late-association path shows up as missing popup pixels rather than
|
|
* needing a real toolkit.
|
|
*
|
|
* usage: xpopup [framew frameth popup-delay-s [unmap-after-s]]
|
|
*
|
|
* After mapping, the popup repaints continuously (as real menus do while
|
|
* animating), forcing damage so server-side offload keeps committing.
|
|
*
|
|
* The frame is muted blue (0x306090); the popup is green (0x00C000) at
|
|
* 300x200, placed at (100,100) inside the frame.
|
|
*/
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xutil.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
|
|
#define POPUP_W 300
|
|
#define POPUP_H 200
|
|
|
|
static void set_pid(Display *d, Window w) {
|
|
pid_t pid = getpid();
|
|
Atom net_pid = XInternAtom(d, "_NET_WM_PID", False);
|
|
XChangeProperty(d, w, net_pid, XA_CARDINAL, 32, PropModeReplace,
|
|
(unsigned char *)&pid, 1);
|
|
}
|
|
|
|
static void fill(Display *d, Window w, unsigned long colour, int wpx, int hpx)
|
|
{
|
|
XGCValues gcv;
|
|
GC gc = XCreateGC(d, w, 0, &gcv);
|
|
XSetForeground(d, gc, colour);
|
|
XFillRectangle(d, w, gc, 0, 0, wpx, hpx);
|
|
XFreeGC(d, gc);
|
|
}
|
|
|
|
static void repaint_frame(Display *d, Window frame, int fw, int fh)
|
|
{
|
|
/* Keep the frame painted after resizes; leave the popup rectangle to
|
|
the popup window itself (it floats above us). */
|
|
fill(d, frame, 0x306090UL, fw, fh);
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int fw = argc > 1 ? atoi(argv[1]) : 640;
|
|
int fh = argc > 2 ? atoi(argv[2]) : 480;
|
|
int delay_s = argc > 3 ? atoi(argv[3]) : 1;
|
|
int unmap_after = argc > 4 ? atoi(argv[4]) : -1; /* seconds, -1 = never */
|
|
|
|
Display *d = XOpenDisplay(NULL);
|
|
if (!d) {
|
|
fprintf(stderr, "xpopup: cannot open display\n");
|
|
return 1;
|
|
}
|
|
int scr = DefaultScreen(d);
|
|
Window root = RootWindow(d, scr);
|
|
|
|
Window frame = XCreateSimpleWindow(d, root, 0, 0, fw, fh, 0,
|
|
BlackPixel(d, scr),
|
|
0x306090UL);
|
|
XStoreName(d, frame, "xpopup-frame");
|
|
set_pid(d, frame);
|
|
|
|
Window popup = XCreateSimpleWindow(d, root, 100, 100, POPUP_W, POPUP_H, 0,
|
|
BlackPixel(d, scr),
|
|
0x00C000UL);
|
|
XStoreName(d, popup, "xpopup-menu");
|
|
set_pid(d, popup);
|
|
/* Managed-window menu shape: transient for the frame, never OR. */
|
|
XSetTransientForHint(d, popup, frame);
|
|
|
|
XSelectInput(d, frame, ExposureMask | StructureNotifyMask);
|
|
|
|
XMapWindow(d, frame);
|
|
XFlush(d);
|
|
|
|
/* Map the "menu" once things have settled. */
|
|
sleep((unsigned)delay_s);
|
|
XMapWindow(d, popup);
|
|
|
|
/* Event pump with a repaint tick: the popup refreshes continuously
|
|
(menu-like damage); the frame refills on Expose. */
|
|
time_t popup_mapped_at = time(NULL);
|
|
int popup_up = 1;
|
|
for (;;) {
|
|
while (XPending(d) > 0) {
|
|
XEvent ev;
|
|
XNextEvent(d, &ev);
|
|
if (ev.xany.window == frame && ev.type == Expose)
|
|
repaint_frame(d, frame, fw, fh);
|
|
else if (ev.xany.window == popup &&
|
|
(ev.type == ConfigureNotify || ev.type == Expose))
|
|
fill(d, popup, 0x00C000UL, POPUP_W, POPUP_H);
|
|
}
|
|
if (popup_up) {
|
|
fill(d, popup, 0x00C000UL, POPUP_W, POPUP_H);
|
|
static int checked = 0;
|
|
if (!checked && time(NULL) - popup_mapped_at > 2) {
|
|
checked = 1;
|
|
XImage *img = XGetImage(d, popup, 0, 0, 8, 8, AllPlanes,
|
|
ZPixmap);
|
|
if (img) {
|
|
unsigned long pxl = XGetPixel(img, 4, 4);
|
|
fprintf(stderr, "[xpopup] self-check pixel=%06lx (%s)\n",
|
|
pxl, pxl == 0xC000 ? "GREEN" : "NOT-green");
|
|
XDestroyImage(img);
|
|
}
|
|
}
|
|
if (unmap_after > 0 &&
|
|
(int)(time(NULL) - popup_mapped_at) >= unmap_after) {
|
|
XUnmapWindow(d, popup);
|
|
popup_up = 0;
|
|
}
|
|
}
|
|
XFlush(d);
|
|
struct timespec tick = { 0, 150 * 1000000L };
|
|
nanosleep(&tick, NULL); }
|
|
return 0;
|
|
}
|