kick/tests/xdeco.c

192 lines
6.6 KiB
C
Raw Normal View History

/* 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 <fcntl.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#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, &registry_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;
}