Commands fork one at a time, each gated on the previous one's client connecting, so tab order always mirrors the command-line order (no pid matching, no /proc ancestry). The window stays black until the last command's tab maps, which is then crowned active. A new tab never steals focus on its own: only Ctrl+Shift+T terminals, sub-windows of the tab currently shown, and the startup crown activate. Sub-windows (a second toplevel from an already-connected client) get their own slot glued right after their parent tab. A command that never connects stalls the chain and the crown, leaving the window black (accepted). Tests: wait_any_active waits for the single deterministic crown; an externally-spawned client is now born hidden (cycle to it). Add wltest --extra (same-client second toplevel) plus tests for sub-window placement/focus and Ctrl+Shift+T focus-on-map. Update README.
381 lines
No EOL
13 KiB
C
381 lines
No EOL
13 KiB
C
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <unistd.h>
|
|
|
|
#include <sys/shm.h>
|
|
#include <wayland-client.h>
|
|
#include "xdg-shell-client.h"
|
|
|
|
/* Minimal deterministic xdg toplevel test client: shows a solid-color shm
|
|
buffer and runs until killed. Used by test.sh to give the smoke test two
|
|
visually distinct, cheaply-backed tabs (a SIGUSR1 scene dump can then
|
|
assert which tab is actually displayed). */
|
|
|
|
static struct wl_display *display;
|
|
static struct wl_compositor *compositor;
|
|
static struct wl_shm *shm;
|
|
static struct xdg_wm_base *wm_base;
|
|
static struct wl_surface *surface;
|
|
static struct xdg_surface *xdg_surface;
|
|
static struct xdg_toplevel *toplevel;
|
|
static struct wl_buffer *buffer;
|
|
static struct wl_seat *seat;
|
|
static struct wl_keyboard *keyboard;
|
|
static bool fullscreen;
|
|
|
|
static int W = 1280, H = 800;
|
|
static uint8_t PIXEL[4] = { 0xff, 0x00, 0x00, 0xff }; /* RGBA */
|
|
|
|
/* Optional second toplevel from the SAME client (--extra <hex>): lets the
|
|
smoke tests exercise the compositor's sub-window handling (a second
|
|
toplevel of an already-structured tab gets its own slot glued right after
|
|
its parent). */
|
|
static bool want_extra;
|
|
static bool extra_done;
|
|
static uint8_t EXTRA_PIXEL[4] = { 0x00, 0xff, 0x00, 0xff };
|
|
static int EX_W = 640, EX_H = 400;
|
|
static struct wl_surface *ex_surface;
|
|
static struct xdg_surface *ex_xdg;
|
|
static struct xdg_toplevel *ex_toplevel;
|
|
static struct wl_buffer *ex_buffer;
|
|
|
|
static void create_buffer(void);
|
|
static void extra_open(void);
|
|
|
|
static void keyboard_keymap(void *data, struct wl_keyboard *kb,
|
|
uint32_t format, int32_t fd, uint32_t size) {
|
|
(void)data; (void)kb; (void)format; (void)size;
|
|
close(fd);
|
|
}
|
|
|
|
static void keyboard_key(void *data, struct wl_keyboard *kb, uint32_t serial,
|
|
uint32_t time, uint32_t key, uint32_t state) {
|
|
(void)data; (void)kb; (void)serial; (void)time;
|
|
/* 'F' (evdev 33) toggles fullscreen, giving the smoke tests a real
|
|
client-initiated fullscreen request to feed the compositor. */
|
|
if (key == 33 && state == WL_KEYBOARD_KEY_STATE_PRESSED) {
|
|
fullscreen = !fullscreen;
|
|
if (fullscreen)
|
|
xdg_toplevel_set_fullscreen(toplevel, NULL);
|
|
else
|
|
xdg_toplevel_unset_fullscreen(toplevel);
|
|
}
|
|
}
|
|
|
|
static void keyboard_enter(void *data, struct wl_keyboard *kb, uint32_t serial,
|
|
struct wl_surface *srf, struct wl_array *keys) {
|
|
(void)data; (void)kb; (void)serial; (void)srf; (void)keys;
|
|
}
|
|
|
|
static void keyboard_leave(void *data, struct wl_keyboard *kb, uint32_t serial,
|
|
struct wl_surface *srf) {
|
|
(void)data; (void)kb; (void)serial; (void)srf;
|
|
}
|
|
|
|
static void keyboard_modifiers(void *data, struct wl_keyboard *kb,
|
|
uint32_t serial, uint32_t mods_depressed,
|
|
uint32_t mods_latched, uint32_t mods_locked,
|
|
uint32_t group) {
|
|
(void)data; (void)kb; (void)serial; (void)mods_depressed;
|
|
(void)mods_latched; (void)mods_locked; (void)group;
|
|
}
|
|
|
|
static void keyboard_repeat_info(void *data, struct wl_keyboard *kb,
|
|
int32_t rate, int32_t delay) {
|
|
(void)data; (void)kb; (void)rate; (void)delay;
|
|
}
|
|
|
|
static const struct wl_keyboard_listener keyboard_listener = {
|
|
.keymap = keyboard_keymap,
|
|
.enter = keyboard_enter,
|
|
.leave = keyboard_leave,
|
|
.key = keyboard_key,
|
|
.modifiers = keyboard_modifiers,
|
|
.repeat_info = keyboard_repeat_info,
|
|
};
|
|
|
|
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *wm, uint32_t serial) {
|
|
(void)data;
|
|
xdg_wm_base_pong(wm, serial);
|
|
}
|
|
|
|
static const struct xdg_wm_base_listener wm_base_listener = {
|
|
.ping = xdg_wm_base_ping,
|
|
};
|
|
|
|
static void xdg_surface_configure(void *data, struct xdg_surface *xs, uint32_t serial) {
|
|
(void)data;
|
|
xdg_surface_ack_configure(xs, serial);
|
|
create_buffer();
|
|
wl_surface_attach(surface, buffer, 0, 0);
|
|
wl_surface_damage_buffer(surface, 0, 0, W, H);
|
|
wl_surface_commit(surface);
|
|
if (want_extra && !extra_done) {
|
|
extra_done = true;
|
|
extra_open();
|
|
}
|
|
}
|
|
|
|
static const struct xdg_surface_listener xdg_surface_listener = {
|
|
.configure = xdg_surface_configure,
|
|
};
|
|
|
|
static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xp, int32_t w,
|
|
int32_t h, struct wl_array *states) {
|
|
(void)data; (void)xp; (void)states;
|
|
if (w > 0 && h > 0) { W = w; H = h; }
|
|
}
|
|
|
|
static void xdg_toplevel_close(void *data, struct xdg_toplevel *xp) {
|
|
(void)data; (void)xp;
|
|
exit(0);
|
|
}
|
|
|
|
static const struct xdg_toplevel_listener toplevel_listener = {
|
|
.configure = xdg_toplevel_configure,
|
|
.close = xdg_toplevel_close,
|
|
};
|
|
|
|
static void registry_global(void *data, struct wl_registry *reg, uint32_t name,
|
|
const char *interface, uint32_t version) {
|
|
(void)data;
|
|
if (strcmp(interface, wl_compositor_interface.name) == 0) {
|
|
compositor = wl_registry_bind(reg, name, &wl_compositor_interface,
|
|
version < 4 ? version : 4);
|
|
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
|
|
shm = wl_registry_bind(reg, name, &wl_shm_interface,
|
|
version < 1 ? version : 1);
|
|
} else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
|
|
wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface,
|
|
version < 1 ? version : 1);
|
|
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
|
|
seat = wl_registry_bind(reg, name, &wl_seat_interface,
|
|
version < 5 ? version : 5);
|
|
}
|
|
}
|
|
|
|
static void registry_global_remove(void *data, struct wl_registry *reg,
|
|
uint32_t name) {
|
|
(void)data; (void)reg; (void)name;
|
|
}
|
|
|
|
static const struct wl_registry_listener registry_listener = {
|
|
.global = registry_global,
|
|
.global_remove = registry_global_remove,
|
|
};
|
|
|
|
static struct wl_shm_pool *make_pool(size_t size, int *fd_out) {
|
|
char name[] = "/tmp/wltest-XXXXXX";
|
|
int fd = mkstemp(name);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "wltest: mkstemp failed\n");
|
|
return NULL;
|
|
}
|
|
unlink(name);
|
|
if (ftruncate(fd, (off_t)size) < 0) {
|
|
fprintf(stderr, "wltest: ftruncate failed\n");
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
*fd_out = fd;
|
|
return wl_shm_create_pool(shm, fd, (int32_t)size);
|
|
}
|
|
|
|
static void create_buffer(void) {
|
|
if (buffer) return;
|
|
size_t stride = (size_t)W * 4;
|
|
size_t size = stride * (size_t)H;
|
|
int fd;
|
|
struct wl_shm_pool *pool = make_pool(size, &fd);
|
|
if (!pool) {
|
|
fprintf(stderr, "wltest: shm pool failed\n");
|
|
exit(1);
|
|
}
|
|
uint8_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (data == MAP_FAILED) {
|
|
fprintf(stderr, "wltest: mmap failed\n");
|
|
exit(1);
|
|
}
|
|
for (int y = 0; y < H; y++) {
|
|
uint8_t *row = data + (size_t)y * stride;
|
|
for (int x = 0; x < W; x++) {
|
|
row[x*4+0] = PIXEL[2]; /* B */
|
|
row[x*4+1] = PIXEL[1]; /* G */
|
|
row[x*4+2] = PIXEL[0]; /* R */
|
|
row[x*4+3] = PIXEL[3]; /* A */
|
|
}
|
|
}
|
|
munmap(data, size);
|
|
close(fd);
|
|
buffer = wl_shm_pool_create_buffer(pool, 0, W, H, (int32_t)stride,
|
|
WL_SHM_FORMAT_XRGB8888);
|
|
wl_shm_pool_destroy(pool);
|
|
}
|
|
|
|
/* ---- the extra (second, same-process) toplevel ---- */
|
|
|
|
static void extra_toplevel_configure(void *data, struct xdg_toplevel *xp,
|
|
int32_t w, int32_t h, struct wl_array *states) {
|
|
(void)data; (void)xp; (void)w; (void)h; (void)states;
|
|
}
|
|
|
|
static const struct xdg_toplevel_listener extra_toplevel_listener = {
|
|
.configure = extra_toplevel_configure,
|
|
.close = xdg_toplevel_close,
|
|
};
|
|
|
|
static void extra_create_buffer(void) {
|
|
if (ex_buffer) return;
|
|
size_t stride = (size_t)EX_W * 4;
|
|
size_t size = stride * (size_t)EX_H;
|
|
int fd;
|
|
struct wl_shm_pool *pool = make_pool(size, &fd);
|
|
if (!pool) {
|
|
fprintf(stderr, "wltest: extra shm pool failed\n");
|
|
exit(1);
|
|
}
|
|
uint8_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (data == MAP_FAILED) {
|
|
fprintf(stderr, "wltest: extra mmap failed\n");
|
|
exit(1);
|
|
}
|
|
for (int y = 0; y < EX_H; y++) {
|
|
uint8_t *row = data + (size_t)y * stride;
|
|
for (int x = 0; x < EX_W; x++) {
|
|
row[x*4+0] = EXTRA_PIXEL[2]; /* B */
|
|
row[x*4+1] = EXTRA_PIXEL[1]; /* G */
|
|
row[x*4+2] = EXTRA_PIXEL[0]; /* R */
|
|
row[x*4+3] = EXTRA_PIXEL[3]; /* A */
|
|
}
|
|
}
|
|
munmap(data, size);
|
|
close(fd);
|
|
ex_buffer = wl_shm_pool_create_buffer(pool, 0, EX_W, EX_H,
|
|
(int32_t)stride, WL_SHM_FORMAT_XRGB8888);
|
|
wl_shm_pool_destroy(pool);
|
|
}
|
|
|
|
static void extra_xdg_configure(void *data, struct xdg_surface *xs, uint32_t serial) {
|
|
(void)data;
|
|
xdg_surface_ack_configure(xs, serial);
|
|
extra_create_buffer();
|
|
wl_surface_attach(ex_surface, ex_buffer, 0, 0);
|
|
wl_surface_damage_buffer(ex_surface, 0, 0, EX_W, EX_H);
|
|
wl_surface_commit(ex_surface);
|
|
}
|
|
|
|
static const struct xdg_surface_listener extra_xdg_listener = {
|
|
.configure = extra_xdg_configure,
|
|
};
|
|
|
|
static void extra_open(void) {
|
|
ex_surface = wl_compositor_create_surface(compositor);
|
|
ex_xdg = xdg_wm_base_get_xdg_surface(wm_base, ex_surface);
|
|
ex_toplevel = xdg_surface_get_toplevel(ex_xdg);
|
|
xdg_toplevel_set_title(ex_toplevel, "extra");
|
|
xdg_surface_add_listener(ex_xdg, &extra_xdg_listener, NULL);
|
|
xdg_toplevel_add_listener(ex_toplevel, &extra_toplevel_listener, NULL);
|
|
wl_surface_commit(ex_surface);
|
|
}
|
|
|
|
static void print_usage(const char *argv0) {
|
|
fprintf(stderr, "usage: %s [--app-id <id>] [--extra #RRGGBB] #RRGGBB [WIDTHxHEIGHT]\n"
|
|
" %s shows a solid-color xdg_toplevel, e.g. #ff0000\n"
|
|
" --extra also opens a second toplevel from the same client\n",
|
|
argv0, argv0);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
const char *app_id = NULL;
|
|
int i = 1;
|
|
while (i < argc) {
|
|
if (strcmp(argv[i], "--app-id") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "wltest: --app-id needs a value\n");
|
|
return 1;
|
|
}
|
|
app_id = argv[i + 1];
|
|
i += 2;
|
|
} else if (strcmp(argv[i], "--extra") == 0) {
|
|
if (i + 1 >= argc) {
|
|
fprintf(stderr, "wltest: --extra needs a value\n");
|
|
return 1;
|
|
}
|
|
const char *x = argv[i + 1];
|
|
if (*x == '#') x++;
|
|
if (strlen(x) != 6 || sscanf(x, "%2hhx%2hhx%2hhx",
|
|
&EXTRA_PIXEL[0], &EXTRA_PIXEL[1], &EXTRA_PIXEL[2]) != 3) {
|
|
fprintf(stderr, "wltest: bad extra color '%s'\n", argv[i + 1]);
|
|
return 1;
|
|
}
|
|
want_extra = true;
|
|
i += 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= argc || argv[i][0] != '#') {
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
const char *hex = argv[i] + 1;
|
|
if (strlen(hex) != 6 || sscanf(hex, "%2hhx%2hhx%2hhx",
|
|
&PIXEL[0], &PIXEL[1], &PIXEL[2]) != 3) {
|
|
fprintf(stderr, "wltest: bad color '%s'\n", argv[i]);
|
|
return 1;
|
|
}
|
|
if (i + 2 < argc) {
|
|
W = atoi(argv[i + 1]);
|
|
H = atoi(argv[i + 2]);
|
|
if (W <= 0 || H <= 0) {
|
|
fprintf(stderr, "wltest: bad size\n");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
display = wl_display_connect(NULL);
|
|
if (!display) {
|
|
fprintf(stderr, "wltest: cannot connect (WAYLAND_DISPLAY=%s)\n",
|
|
getenv("WAYLAND_DISPLAY") ? getenv("WAYLAND_DISPLAY") : "unset");
|
|
return 1;
|
|
}
|
|
|
|
struct wl_registry *registry = wl_display_get_registry(display);
|
|
wl_registry_add_listener(registry, ®istry_listener, NULL);
|
|
wl_display_roundtrip(display);
|
|
|
|
if (!compositor || !shm || !wm_base) {
|
|
fprintf(stderr, "wltest: compositor/shm/xdg_wm_base missing\n");
|
|
return 1;
|
|
}
|
|
|
|
surface = wl_compositor_create_surface(compositor);
|
|
xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface);
|
|
toplevel = xdg_surface_get_toplevel(xdg_surface);
|
|
xdg_toplevel_set_title(toplevel, argv[i]);
|
|
if (app_id)
|
|
xdg_toplevel_set_app_id(toplevel, app_id);
|
|
|
|
xdg_wm_base_add_listener(wm_base, &wm_base_listener, NULL);
|
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, NULL);
|
|
xdg_toplevel_add_listener(toplevel, &toplevel_listener, NULL);
|
|
|
|
if (seat) {
|
|
keyboard = wl_seat_get_keyboard(seat);
|
|
wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL);
|
|
}
|
|
|
|
wl_surface_commit(surface);
|
|
wl_display_roundtrip(display);
|
|
|
|
while (wl_display_dispatch(display) >= 0) {
|
|
}
|
|
return 0;
|
|
} |