192 lines
6.6 KiB
C
192 lines
6.6 KiB
C
|
|
/* 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, ®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;
|
||
|
|
}
|