Add stdin event feed and smoke test suite
Compositor: read raw keyboard records from stdin and dispatch them into the real host_kbd_key/host_kbd_modifiers handlers, so keybindings (F11, Ctrl+PageUp/Down, F9/F10 dumps) can be driven without a focused host window or wtype. Binary wire: 1|u32 key|u32 state, 2|u32 mods|... Tests: tests/wltest.c solid-colour xdg_toplevel client, plus a pytest suite (tests/test_smoke.py) that launches nested instances per test and feeds events through the new channel. test.sh runs it. F11 fullscreen toggle now logs its new state.
This commit is contained in:
parent
c8ea4c36a3
commit
2b034f990f
5 changed files with 584 additions and 0 deletions
203
tests/wltest.c
Normal file
203
tests/wltest.c
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#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 (the compositor's F9 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 int W = 1280, H = 800;
|
||||
static uint8_t PIXEL[4] = { 0xff, 0x00, 0x00, 0xff }; /* RGBA */
|
||||
|
||||
static void create_buffer(void);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static void print_usage(const char *argv0) {
|
||||
fprintf(stderr, "usage: %s #RRGGBB [WIDTHxHEIGHT]\n"
|
||||
" %s shows a solid-color xdg_toplevel, e.g. #ff0000\n",
|
||||
argv0, argv0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2 || argv[1][0] != '#') {
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
const char *hex = argv[1] + 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[1]);
|
||||
return 1;
|
||||
}
|
||||
if (argc >= 4) {
|
||||
W = atoi(argv[2]);
|
||||
H = atoi(argv[3]);
|
||||
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[1]);
|
||||
|
||||
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);
|
||||
|
||||
wl_surface_commit(surface);
|
||||
wl_display_roundtrip(display);
|
||||
|
||||
while (wl_display_dispatch(display) >= 0) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue