kick/tests/xreqs.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;
}