/* xdeco: xdg-decoration mode probe. * * Binds zxdg_decoration_manager_v1, maps one xdg toplevel, and reports the * decoration mode the compositor configures. kick pins every tab * client-side (it draws no frames itself), so the probe exits 0 only when * the configure carries ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE. * * usage: xdeco (prints "mode=N" to stdout; prints "no-decoration" and * exits 2 when the compositor lacks the global, so the test can skip * instead of failing) */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include "xdg-shell-client.h" #include "xdg-decoration-client.h" static struct wl_compositor *compositor; static struct wl_shm *shm; static struct xdg_wm_base *wm_base; static struct zxdg_decoration_manager_v1 *deco_mgr; static struct wl_buffer *buf; static int deco_mode = -1; /* -1 = no decoration configure yet */ /* One grey 8x8 shm buffer: the probe must map (not just commit) because kick pins client-side at map time -- set_mode asserts on a surface that never committed, so pre-commit pinning would abort wlroots. */ static struct wl_buffer *make_buffer(void) { static int seq; char name[64]; snprintf(name, sizeof name, "/xdeco-%d-%d", getpid(), seq++); int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd < 0) return NULL; shm_unlink(name); if (ftruncate(fd, 8 * 8 * 4) < 0) { close(fd); return NULL; } void *px = mmap(NULL, 8 * 8 * 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (px == MAP_FAILED) { close(fd); return NULL; } memset(px, 0x80, 8 * 8 * 4); munmap(px, 8 * 8 * 4); struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, 8 * 8 * 4); struct wl_buffer *b = wl_shm_pool_create_buffer(pool, 0, 8, 8, 8 * 4, WL_SHM_FORMAT_XRGB8888); wl_shm_pool_destroy(pool); close(fd); return b; } static double now_s(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; } 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, 1); } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { wm_base = wl_registry_bind(reg, name, &xdg_wm_base_interface, 1); } else if (strcmp(interface, zxdg_decoration_manager_v1_interface.name) == 0) { deco_mgr = wl_registry_bind(reg, name, &zxdg_decoration_manager_v1_interface, 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 void wm_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_listener = { .ping = wm_ping, }; static struct wl_surface *surface; static struct xdg_surface *xdg_surface; static void surface_configure(void *data, struct xdg_surface *xs, uint32_t serial) { (void)data; xdg_surface_ack_configure(xs, serial); if (buf) { wl_surface_attach(surface, buf, 0, 0); wl_surface_damage(surface, 0, 0, 8, 8); } wl_surface_commit(surface); } static const struct xdg_surface_listener surface_listener = { .configure = surface_configure, }; static void deco_configure(void *data, struct zxdg_toplevel_decoration_v1 *deco, uint32_t mode) { (void)data; (void)deco; deco_mode = (int)mode; } static const struct zxdg_toplevel_decoration_v1_listener deco_listener = { .configure = deco_configure, }; int main(void) { struct wl_display *display = wl_display_connect(NULL); if (!display) { fprintf(stderr, "xdeco: cannot connect to Wayland display\n"); return 1; } struct wl_registry *reg = wl_display_get_registry(display); wl_registry_add_listener(reg, ®istry_listener, NULL); if (wl_display_roundtrip(display) < 0) return 1; if (!compositor || !wm_base || !shm) { fprintf(stderr, "xdeco: missing compositor/wm_base/shm\n"); return 1; } if (!deco_mgr) { printf("no-decoration\n"); fflush(stdout); return 2; } xdg_wm_base_add_listener(wm_base, &wm_listener, NULL); surface = wl_compositor_create_surface(compositor); xdg_surface = xdg_wm_base_get_xdg_surface(wm_base, surface); xdg_surface_add_listener(xdg_surface, &surface_listener, NULL); struct xdg_toplevel *toplevel = xdg_surface_get_toplevel(xdg_surface); xdg_toplevel_set_app_id(toplevel, "xdeco-probe"); xdg_toplevel_set_title(toplevel, "xdeco"); struct zxdg_toplevel_decoration_v1 *deco = zxdg_decoration_manager_v1_get_toplevel_decoration(deco_mgr, toplevel); zxdg_toplevel_decoration_v1_add_listener(deco, &deco_listener, NULL); buf = make_buffer(); /* First commit stays bufferless (a buffer before the initial configure is a protocol error); the buffer attaches in surface_configure, which maps the window and lets kick pin client-side at map time. */ wl_surface_commit(surface); double deadline = now_s() + 10.0; int fd = wl_display_get_fd(display); while (deco_mode < 0 && now_s() < deadline) { wl_display_flush(display); struct pollfd pfd = { .fd = fd, .events = POLLIN }; int ms = (int)((deadline - now_s()) * 1000.0); if (ms < 0) break; if (poll(&pfd, 1, ms) < 0) break; if (wl_display_dispatch(display) < 0) break; } printf("mode=%d\n", deco_mode); fflush(stdout); wl_display_disconnect(display); return deco_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE ? 0 : 1; }