190 lines
6.7 KiB
C
190 lines
6.7 KiB
C
/* 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 <X11/Xlib.h>
|
|
#include <X11/Xatom.h>
|
|
#include <X11/Xutil.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
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;
|
|
}
|