/* xreqs: X11 client-request probe for the compositor's xwayland handlers. * * usage: * xreqs managed -- map a titled _NET_WM_PID window, then drive it through * every client-initiated request the compositor handles: * retitle, minimize, fullscreen, move/resize, raise, * maximize and finally a WM_DELETE_WINDOW close, which * tears its tab down. Sleeps pace each step so the * driver can assert the compositor's reactions. * xreqs unmapped -- create the same window but never map it, then * destroy it and exit: the compositor must drop the * still-pending surface without a trace. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include static void set_pid(Display *d, Window w) { pid_t pid = getpid(); Atom a = XInternAtom(d, "_NET_WM_PID", False); XChangeProperty(d, w, a, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&pid, 1); } static void net_wm_state(Display *d, Window w, long action, Atom state) { Atom wm_state = XInternAtom(d, "_NET_WM_STATE", False); XEvent ev; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = w; ev.xclient.message_type = wm_state; ev.xclient.format = 32; ev.xclient.data.l[0] = action; ev.xclient.data.l[1] = (long)state; ev.xclient.data.l[2] = 0; ev.xclient.data.l[3] = 1; /* normal application source */ XSendEvent(d, DefaultRootWindow(d), False, SubstructureRedirectMask | SubstructureNotifyMask, &ev); XFlush(d); } static int managed_main(void) { Display *d = XOpenDisplay(NULL); if (!d) { fprintf(stderr, "xreqs: cannot open display\n"); return 1; } int scr = DefaultScreen(d); Window w = XCreateSimpleWindow(d, RootWindow(d, scr), 0, 0, 400, 300, 0, BlackPixel(d, scr), 0x208040UL); XStoreName(d, w, "xreqs-frame"); set_pid(d, w); XClassHint *ch = XAllocClassHint(); ch->res_name = "xreqs-probe"; ch->res_class = "XReqsProbe"; XSetClassHint(d, w, ch); XFree(ch); Atom protos[] = { XInternAtom(d, "WM_DELETE_WINDOW", False), }; XSetWMProtocols(d, w, protos, 1); XMapWindow(d, w); XFlush(d); fprintf(stderr, "[xreqs] mapped\n"); sleep(3); /* let the compositor claim and map the tab */ XStoreName(d, w, "xreqs-retitled"); XFlush(d); fprintf(stderr, "[xreqs] retitled\n"); sleep(1); Atom fs = XInternAtom(d, "_NET_WM_STATE_FULLSCREEN", False); net_wm_state(d, w, 1 /* ADD */, fs); fprintf(stderr, "[xreqs] fullscreened\n"); sleep(2); net_wm_state(d, w, 0 /* REMOVE */, fs); fprintf(stderr, "[xreqs] unfullscreened\n"); sleep(1); XMoveResizeWindow(d, w, 50, 50, 500, 400); XFlush(d); fprintf(stderr, "[xreqs] moved+resized\n"); sleep(1); XRaiseWindow(d, w); XSetInputFocus(d, w, RevertToParent, CurrentTime); XFlush(d); /* A pager-style activation request: this is what the compositor honors with a tab switch (a raw raise/focus does not emit it). */ Atom net_active = XInternAtom(d, "_NET_ACTIVE_WINDOW", False); XEvent aev; memset(&aev, 0, sizeof(aev)); aev.xclient.type = ClientMessage; aev.xclient.window = w; aev.xclient.message_type = net_active; aev.xclient.format = 32; aev.xclient.data.l[0] = 1; /* normal application */ aev.xclient.data.l[1] = CurrentTime; XSendEvent(d, DefaultRootWindow(d), False, SubstructureRedirectMask | SubstructureNotifyMask, &aev); XFlush(d); fprintf(stderr, "[xreqs] raised\n"); sleep(2); Atom maxv = XInternAtom(d, "_NET_WM_STATE_MAXIMIZED_VERT", False); Atom maxh = XInternAtom(d, "_NET_WM_STATE_MAXIMIZED_HORZ", False); /* The tab starts maximized (the compositor configures it so), and the WM only reports changes: REMOVE first to flip the state, then ADD. */ XEvent mev; memset(&mev, 0, sizeof(mev)); mev.xclient.type = ClientMessage; mev.xclient.window = w; mev.xclient.message_type = XInternAtom(d, "_NET_WM_STATE", False); mev.xclient.format = 32; mev.xclient.data.l[0] = 0; /* REMOVE */ mev.xclient.data.l[1] = (long)maxv; mev.xclient.data.l[2] = (long)maxh; mev.xclient.data.l[3] = 1; XSendEvent(d, DefaultRootWindow(d), False, SubstructureRedirectMask | SubstructureNotifyMask, &mev); XFlush(d); sleep(1); mev.xclient.data.l[0] = 1; /* ADD */ XSendEvent(d, DefaultRootWindow(d), False, SubstructureRedirectMask | SubstructureNotifyMask, &mev); XFlush(d); fprintf(stderr, "[xreqs] maximized\n"); sleep(2); /* Minimized last: the WM may unmap on iconify, and every later step wants a mapped window. */ XIconifyWindow(d, w, scr); XFlush(d); fprintf(stderr, "[xreqs] minimized\n"); sleep(1); /* Ask to close the way a pager would: _NET_CLOSE_WINDOW on the root window names our window, and the WM turns it into a close request that tears our tab down. */ Atom net_close = XInternAtom(d, "_NET_CLOSE_WINDOW", False); XEvent ev; memset(&ev, 0, sizeof(ev)); ev.xclient.type = ClientMessage; ev.xclient.window = w; ev.xclient.message_type = net_close; ev.xclient.format = 32; ev.xclient.data.l[0] = CurrentTime; ev.xclient.data.l[1] = 1; /* normal application source */ XSendEvent(d, DefaultRootWindow(d), False, SubstructureRedirectMask | SubstructureNotifyMask, &ev); XFlush(d); fprintf(stderr, "[xreqs] close requested\n"); sleep(3); return 0; } static int unmapped_main(void) { Display *d = XOpenDisplay(NULL); if (!d) { fprintf(stderr, "xreqs: cannot open display\n"); return 1; } int scr = DefaultScreen(d); Window w = XCreateSimpleWindow(d, RootWindow(d, scr), 0, 0, 400, 300, 0, BlackPixel(d, scr), 0x802020UL); XStoreName(d, w, "xreqs-unmapped"); set_pid(d, w); XFlush(d); fprintf(stderr, "[xreqs] created unmapped\n"); sleep(3); /* never mapped: the compositor only ever pends this one */ XDestroyWindow(d, w); XFlush(d); fprintf(stderr, "[xreqs] destroyed unmapped\n"); sleep(1); return 0; } int main(int argc, char **argv) { if (argc > 1 && strcmp(argv[1], "managed") == 0) return managed_main(); if (argc > 1 && strcmp(argv[1], "unmapped") == 0) return unmapped_main(); fprintf(stderr, "usage: xreqs managed | unmapped\n"); return 2; }