/*
 * backend-wayland.c - wlroots backend.
 *
 * Overlay   : zwlr_layer_shell_v1, one fullscreen surface per output with
 *             exclusive keyboard interactivity - that is the keyboard grab.
 * Pointer   : zwlr_virtual_pointer_v1 (absolute motion + buttons).
 * Keyboard  : wl_keyboard keymap fed to xkbcommon for keysym translation.
 * Buffers   : double-buffered wl_shm ARGB8888, drawn by the core's renderer.
 */
#define _GNU_SOURCE
#include "platform.h"

#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>

#include <wayland-client.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-keysyms.h>
#include <linux/input-event-codes.h>

#include "xdg-shell-protocol.h"
#include "xdg-output-unstable-v1-protocol.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
#include "wlr-virtual-pointer-unstable-v1-protocol.h"

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define KQ_SIZE 64
#define MAX_OUTPUT_SCALE 16
#define MAX_KEYMAP_SIZE (16U * 1024U * 1024U)

struct buffer {
	struct wl_buffer *wl;
	uint32_t *data;
	size_t size;
	int busy;
	struct mb_buffer pub;
};

struct output {
	struct wl_state *st;
	struct wl_output *wl;
	struct zxdg_output_v1 *xdg;
	uint32_t name;
	int32_t x, y;		/* global (logical) position */
	int width, height;	/* configured surface size = logical size */
	int scale;		/* integer output scale */
	int configured;

	struct wl_surface *surface;
	struct zwlr_layer_surface_v1 *layer;
	struct buffer bufs[2];
	int cur;
};

struct wl_state {
	struct wl_display *display;
	struct wl_compositor *compositor;
	struct wl_shm *shm;
	struct wl_seat *seat;
	struct wl_keyboard *keyboard;
	struct zwlr_layer_shell_v1 *layer_shell;
	struct zwlr_virtual_pointer_manager_v1 *vp_manager;
	struct zwlr_virtual_pointer_v1 *vp;
	struct zxdg_output_manager_v1 *xdg_output_manager;

	struct output outputs[MB_MAX_SCREENS];
	int n_outputs;
	int configured_count;
	/* Bounding box of every output in logical pixels - the virtual
	 * pointer's coordinate space. Fixed once ready: a later topology
	 * change aborts the run. */
	int bx, by, bw, bh;

	struct xkb_context *xkb_ctx;
	struct xkb_keymap *xkb_map;
	struct xkb_state *xkb_state;

	uint32_t keyq[KQ_SIZE];
	int kq_head, kq_tail;

	int alive;
	int failed;
	int ready;
};

static struct platform g_platform;

static uint32_t now_ms(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (uint32_t)(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}

void platform_sleep_ms(int ms)
{
	struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L };
	nanosleep(&ts, NULL);
}

void platform_early_init(void)
{
	/* Nothing to do: this is an ordinary console program. */
}

static void kq_push(struct wl_state *st, uint32_t k)
{
	int n = (st->kq_tail + 1) % KQ_SIZE;
	if (n == st->kq_head)
		return; /* full; drop */
	st->keyq[st->kq_tail] = k;
	st->kq_tail = n;
}

/* ---- shm buffers ----------------------------------------------------- */

static void buffer_release(void *data, struct wl_buffer *wl)
{
	(void)wl;
	((struct buffer *)data)->busy = 0;
}
static const struct wl_buffer_listener buffer_listener = {
	.release = buffer_release,
};

static void buffer_destroy(struct buffer *b)
{
	if (b->wl)
		wl_buffer_destroy(b->wl);
	if (b->data)
		munmap(b->data, b->size);
	memset(b, 0, sizeof *b);
}

static int buffer_init(struct wl_state *st, struct buffer *b, int w, int h,
		       int scale)
{
	if (w < 1 || h < 1 || scale < 1 || scale > MAX_OUTPUT_SCALE)
		return -1;
	size_t dw = (size_t)w * (size_t)scale;
	size_t dh = (size_t)h * (size_t)scale;
	if (dw > INT32_MAX / 4 || dh > INT32_MAX)
		return -1;
	size_t stride = dw * 4;
	if (dh > INT32_MAX / stride)
		return -1;
	size_t size = stride * dh;
	int fd = memfd_create("mouseboard", MFD_CLOEXEC);
	if (fd < 0)
		return -1;
	if (ftruncate(fd, (off_t)size) < 0) {
		close(fd);
		return -1;
	}
	void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (data == MAP_FAILED) {
		close(fd);
		return -1;
	}
	struct wl_shm_pool *pool = wl_shm_create_pool(st->shm, fd, (int32_t)size);
	b->wl = wl_shm_pool_create_buffer(pool, 0, (int32_t)dw, (int32_t)dh,
					  (int32_t)stride,
					  WL_SHM_FORMAT_ARGB8888);
	wl_shm_pool_destroy(pool);
	close(fd);
	wl_buffer_add_listener(b->wl, &buffer_listener, b);

	b->data = data;
	b->size = size;
	b->busy = 0;
	b->pub.px = data;
	b->pub.w = w;
	b->pub.h = h;
	b->pub.stride = (int)dw;
	b->pub.scale = scale;
	return 0;
}

/* ---- frame interface ------------------------------------------------- */

static struct mb_buffer *wl_frame_begin(struct platform *p, int s)
{
	struct wl_state *st = p->priv;
	struct output *o = &st->outputs[s];

	if (!st->alive)
		return NULL;
	while (o->bufs[0].busy && o->bufs[1].busy) {
		if (!st->alive || wl_display_dispatch(st->display) < 0) {
			fprintf(stderr,
				"mouseboard: display closed while waiting for frame\n");
			return NULL;
		}
	}
	if (!st->alive)
		return NULL;
	int idx = o->bufs[0].busy ? 1 : 0;
	o->cur = idx;
	struct buffer *b = &o->bufs[idx];

	if (!b->wl || b->pub.w != o->width || b->pub.h != o->height ||
	    b->pub.scale != o->scale) {
		buffer_destroy(b);
		if (buffer_init(st, b, o->width, o->height, o->scale) < 0) {
			fprintf(stderr, "mouseboard: shm allocation failed\n");
			return NULL;
		}
	}
	memset(b->data, 0, b->size);
	return &b->pub;
}

static int wl_frame_commit(struct platform *p)
{
	struct wl_state *st = p->priv;
	for (int s = 0; s < st->n_outputs; s++) {
		struct output *o = &st->outputs[s];
		struct buffer *b = &o->bufs[o->cur];
		if (!b->wl)
			continue;
		wl_surface_set_buffer_scale(o->surface, o->scale);
		wl_surface_attach(o->surface, b->wl, 0, 0);
		wl_surface_damage(o->surface, 0, 0, b->pub.w, b->pub.h);
		wl_surface_commit(o->surface);
		b->busy = 1;
	}
	if (wl_display_flush(st->display) < 0 && errno != EAGAIN) {
		fprintf(stderr, "mouseboard: display flush failed: %s\n",
			strerror(errno));
		return -1;
	}
	return 0;
}

/* ---- keyboard -------------------------------------------------------- */

static uint32_t map_sym(xkb_keysym_t sym)
{
	switch (sym) {
	case XKB_KEY_Escape:
		return MB_KEY_ESC;
	case XKB_KEY_BackSpace:
		return MB_KEY_BACKSPACE;
	case XKB_KEY_Return:
	case XKB_KEY_KP_Enter:
		return MB_KEY_ENTER;
	}
	uint32_t u = xkb_keysym_to_utf32(sym);
	if (u >= 0x20 && u < 0x7f)
		return u;
	return MB_KEY_NONE;
}

static void kb_keymap(void *data, struct wl_keyboard *k, uint32_t format,
		      int32_t fd, uint32_t size)
{
	(void)k;
	struct wl_state *st = data;
	/* The keymap can arrive before init has built the xkb context; xkb
	 * dereferences the context, so a NULL one must never reach it. */
	if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 || !st->xkb_ctx) {
		close(fd);
		return;
	}
	struct stat sb;
	if (!size || size > MAX_KEYMAP_SIZE || fstat(fd, &sb) < 0 ||
	    sb.st_size < (off_t)size) {
		fprintf(stderr, "mouseboard: invalid Wayland keymap size\n");
		close(fd);
		return;
	}
	char *map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
	close(fd);
	if (map == MAP_FAILED)
		return;
	struct xkb_keymap *km = xkb_keymap_new_from_buffer(
		st->xkb_ctx, map, size, XKB_KEYMAP_FORMAT_TEXT_V1,
		XKB_KEYMAP_COMPILE_NO_FLAGS);
	munmap(map, size);
	if (!km)
		return;
	struct xkb_state *ks = xkb_state_new(km);
	if (!ks) {
		xkb_keymap_unref(km);
		return;
	}
	if (st->xkb_state)
		xkb_state_unref(st->xkb_state);
	if (st->xkb_map)
		xkb_keymap_unref(st->xkb_map);
	st->xkb_map = km;
	st->xkb_state = ks;
}

static void kb_key(void *data, struct wl_keyboard *k, uint32_t serial,
		   uint32_t time, uint32_t key, uint32_t state)
{
	(void)k;
	(void)serial;
	(void)time;
	struct wl_state *st = data;
	if (state != WL_KEYBOARD_KEY_STATE_PRESSED || !st->xkb_state)
		return;
	xkb_keysym_t sym = xkb_state_key_get_one_sym(st->xkb_state, key + 8);
	uint32_t mb = map_sym(sym);
	if (mb != MB_KEY_NONE)
		kq_push(st, mb);
}

static void kb_modifiers(void *data, struct wl_keyboard *k, uint32_t serial,
			 uint32_t dep, uint32_t lat, uint32_t lock,
			 uint32_t group)
{
	(void)k;
	(void)serial;
	struct wl_state *st = data;
	if (st->xkb_state)
		xkb_state_update_mask(st->xkb_state, dep, lat, lock, 0, 0, group);
}

static void kb_enter(void *d, struct wl_keyboard *k, uint32_t s,
		     struct wl_surface *su, struct wl_array *a)
{
	(void)d; (void)k; (void)s; (void)su; (void)a;
}
static void kb_leave(void *d, struct wl_keyboard *k, uint32_t s,
		     struct wl_surface *su)
{
	(void)d; (void)k; (void)s; (void)su;
}
static void kb_repeat(void *d, struct wl_keyboard *k, int32_t r, int32_t delay)
{
	(void)d; (void)k; (void)r; (void)delay;
}

static const struct wl_keyboard_listener kb_listener = {
	.keymap = kb_keymap,
	.enter = kb_enter,
	.leave = kb_leave,
	.key = kb_key,
	.modifiers = kb_modifiers,
	.repeat_info = kb_repeat,
};

static uint32_t wl_next_key(struct platform *p)
{
	struct wl_state *st = p->priv;
	while (st->kq_head == st->kq_tail) {
		if (!st->alive)
			return MB_KEY_NONE; /* overlay closed */
		wl_display_flush(st->display);
		if (wl_display_dispatch(st->display) < 0)
			return MB_KEY_NONE;
	}
	if (!st->alive)
		return MB_KEY_NONE;
	uint32_t k = st->keyq[st->kq_head];
	st->kq_head = (st->kq_head + 1) % KQ_SIZE;
	return k;
}

/* ---- pointer --------------------------------------------------------- */

static int wl_pointer_move(struct platform *p, int x, int y)
{
	struct wl_state *st = p->priv;
	if (!st->alive || !st->vp)
		return -1;
	int lx = x - st->bx, ly = y - st->by;
	if (lx < 0)
		lx = 0;
	if (ly < 0)
		ly = 0;
	if (lx >= st->bw)
		lx = st->bw - 1;
	if (ly >= st->bh)
		ly = st->bh - 1;
	zwlr_virtual_pointer_v1_motion_absolute(st->vp, now_ms(), (uint32_t)lx,
						(uint32_t)ly, (uint32_t)st->bw,
						(uint32_t)st->bh);
	zwlr_virtual_pointer_v1_frame(st->vp);
	if (wl_display_flush(st->display) < 0 && errno != EAGAIN) {
		fprintf(stderr, "mouseboard: display flush failed: %s\n",
			strerror(errno));
		return -1;
	}
	return 0;
}

static int wl_pointer_button(struct platform *p, enum mb_button b, int press)
{
	struct wl_state *st = p->priv;
	if (!st->alive || !st->vp)
		return -1;
	uint32_t btn = b == MB_LEFT ? BTN_LEFT
		     : b == MB_MIDDLE ? BTN_MIDDLE
				      : BTN_RIGHT;
	zwlr_virtual_pointer_v1_button(st->vp, now_ms(), btn,
				       press ? WL_POINTER_BUTTON_STATE_PRESSED
					     : WL_POINTER_BUTTON_STATE_RELEASED);
	zwlr_virtual_pointer_v1_frame(st->vp);
	if (wl_display_flush(st->display) < 0 && errno != EAGAIN) {
		fprintf(stderr, "mouseboard: display flush failed: %s\n",
			strerror(errno));
		return -1;
	}
	return 0;
}

/* ---- output, seat, layer surface listeners --------------------------- */

static void runtime_changed(struct wl_state *st, const char *what)
{
	if (st->alive) {
		const char *when = st->ready ? "changed while active" :
					       "became unavailable during setup";
		fprintf(stderr, "mouseboard: %s %s\n", what, when);
	}
	st->failed = 1;
	st->alive = 0;
}

static void out_geometry(void *data, struct wl_output *o, int32_t x, int32_t y,
			 int32_t pw, int32_t ph, int32_t subpix,
			 const char *make, const char *model, int32_t tr)
{
	(void)o; (void)pw; (void)ph; (void)subpix; (void)make; (void)model;
	(void)tr;
	struct output *out = data;
	/* xdg-output carries the logical position; comparing against these
	 * physical coordinates would misfire on scaled layouts. */
	if (out->st->xdg_output_manager)
		return;
	if (out->st->ready && (out->x != x || out->y != y)) {
		runtime_changed(out->st, "output geometry");
		return;
	}
	out->x = x;
	out->y = y;
}
static void out_mode(void *d, struct wl_output *o, uint32_t f, int32_t w,
		     int32_t h, int32_t r)
{
	(void)d; (void)o; (void)f; (void)w; (void)h; (void)r;
}
static void out_done(void *d, struct wl_output *o) { (void)d; (void)o; }
static void out_scale(void *d, struct wl_output *o, int32_t f)
{
	(void)o;
	struct output *out = d;
	if (f < 1 || f > MAX_OUTPUT_SCALE) {
		fprintf(stderr, "mouseboard: invalid output scale %d\n", f);
		out->st->failed = 1;
		out->st->alive = 0;
		return;
	}
	if (out->st->ready && out->scale != f) {
		runtime_changed(out->st, "output scale");
		return;
	}
	out->scale = f;
}
static void out_name(void *d, struct wl_output *o, const char *n)
{
	(void)d; (void)o; (void)n;
}
static void out_desc(void *d, struct wl_output *o, const char *n)
{
	(void)d; (void)o; (void)n;
}
static const struct wl_output_listener output_listener = {
	.geometry = out_geometry,
	.mode = out_mode,
	.done = out_done,
	.scale = out_scale,
	.name = out_name,
	.description = out_desc,
};

/* xdg-output gives the authoritative logical position, robust under fractional
 * scaling and transforms; it overrides wl_output.geometry when available. */
static void xdg_out_logical_position(void *data, struct zxdg_output_v1 *xo,
				     int32_t x, int32_t y)
{
	(void)xo;
	struct output *out = data;
	if (out->st->ready && (out->x != x || out->y != y)) {
		runtime_changed(out->st, "output position");
		return;
	}
	out->x = x;
	out->y = y;
}
static void xdg_out_logical_size(void *d, struct zxdg_output_v1 *xo, int32_t w,
				 int32_t h)
{
	(void)d; (void)xo; (void)w; (void)h; /* configure size drives the buffer */
}
static void xdg_out_done(void *d, struct zxdg_output_v1 *xo)
{
	(void)d; (void)xo;
}
static void xdg_out_name(void *d, struct zxdg_output_v1 *xo, const char *n)
{
	(void)d; (void)xo; (void)n;
}
static void xdg_out_desc(void *d, struct zxdg_output_v1 *xo, const char *n)
{
	(void)d; (void)xo; (void)n;
}
static const struct zxdg_output_v1_listener xdg_output_listener = {
	.logical_position = xdg_out_logical_position,
	.logical_size = xdg_out_logical_size,
	.done = xdg_out_done,
	.name = xdg_out_name,
	.description = xdg_out_desc,
};

static void seat_caps(void *data, struct wl_seat *seat, uint32_t caps)
{
	struct wl_state *st = data;
	if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !st->keyboard) {
		st->keyboard = wl_seat_get_keyboard(seat);
		wl_keyboard_add_listener(st->keyboard, &kb_listener, st);
	} else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && st->keyboard) {
		wl_keyboard_release(st->keyboard);
		st->keyboard = NULL;
		runtime_changed(st, "keyboard capability");
	}
}
static void seat_name(void *d, struct wl_seat *s, const char *n)
{
	(void)d; (void)s; (void)n;
}
static const struct wl_seat_listener seat_listener = {
	.capabilities = seat_caps,
	.name = seat_name,
};

static void ls_configure(void *data, struct zwlr_layer_surface_v1 *ls,
			 uint32_t serial, uint32_t w, uint32_t h)
{
	struct output *o = data;
	zwlr_layer_surface_v1_ack_configure(ls, serial);
	if (!w || !h || w > INT_MAX || h > INT_MAX) {
		fprintf(stderr, "mouseboard: invalid output size %ux%u\n", w, h);
		o->st->failed = 1;
		o->st->alive = 0;
		return;
	}
	if (o->st->ready &&
	    (o->width != (int)w || o->height != (int)h)) {
		runtime_changed(o->st, "output size");
		return;
	}
	o->width = (int)w;
	o->height = (int)h;
	if (!o->configured) {
		o->configured = 1;
		o->st->configured_count++;
	}
}
static void ls_closed(void *data, struct zwlr_layer_surface_v1 *ls)
{
	(void)ls;
	((struct output *)data)->st->alive = 0;
}
static const struct zwlr_layer_surface_v1_listener ls_listener = {
	.configure = ls_configure,
	.closed = ls_closed,
};

/* ---- registry -------------------------------------------------------- */

static void reg_global(void *data, struct wl_registry *r, uint32_t name,
		       const char *iface, uint32_t version)
{
	struct wl_state *st = data;
	if (!strcmp(iface, wl_compositor_interface.name)) {
		st->compositor = wl_registry_bind(r, name,
						  &wl_compositor_interface,
						  MIN(version, 4));
	} else if (!strcmp(iface, wl_shm_interface.name)) {
		st->shm = wl_registry_bind(r, name, &wl_shm_interface, 1);
	} else if (!strcmp(iface, wl_seat_interface.name)) {
		st->seat = wl_registry_bind(r, name, &wl_seat_interface,
					    MIN(version, 5));
		wl_seat_add_listener(st->seat, &seat_listener, st);
	} else if (!strcmp(iface, zwlr_layer_shell_v1_interface.name)) {
		st->layer_shell =
			wl_registry_bind(r, name,
					 &zwlr_layer_shell_v1_interface,
					 MIN(version, 4));
	} else if (!strcmp(iface,
			   zwlr_virtual_pointer_manager_v1_interface.name)) {
		st->vp_manager = wl_registry_bind(
			r, name, &zwlr_virtual_pointer_manager_v1_interface,
			MIN(version, 2));
	} else if (!strcmp(iface, zxdg_output_manager_v1_interface.name)) {
		st->xdg_output_manager = wl_registry_bind(
			r, name, &zxdg_output_manager_v1_interface,
			MIN(version, 3));
	} else if (!strcmp(iface, wl_output_interface.name)) {
		if (st->ready) {
			runtime_changed(st, "output topology");
			return;
		}
		if (st->n_outputs >= MB_MAX_SCREENS)
			return;
		struct output *o = &st->outputs[st->n_outputs++];
		memset(o, 0, sizeof *o);
		o->st = st;
		o->name = name;
		o->scale = 1;
		o->wl = wl_registry_bind(r, name, &wl_output_interface,
					 MIN(version, 4));
		wl_output_add_listener(o->wl, &output_listener, o);
	}
}
static void reg_remove(void *d, struct wl_registry *r, uint32_t name)
{
	(void)r;
	struct wl_state *st = d;
	for (int s = 0; s < st->n_outputs; s++) {
		if (st->outputs[s].name == name) {
			runtime_changed(st, "output topology");
			return;
		}
	}
}
static const struct wl_registry_listener reg_listener = {
	.global = reg_global,
	.global_remove = reg_remove,
};

/* ---- teardown -------------------------------------------------------- */

static void wl_teardown(struct platform *p)
{
	struct wl_state *st = p->priv;
	if (!st)
		return;
	for (int s = 0; s < st->n_outputs; s++) {
		struct output *o = &st->outputs[s];
		buffer_destroy(&o->bufs[0]);
		buffer_destroy(&o->bufs[1]);
		if (o->xdg)
			zxdg_output_v1_destroy(o->xdg);
		if (o->layer)
			zwlr_layer_surface_v1_destroy(o->layer);
		if (o->surface)
			wl_surface_destroy(o->surface);
		if (o->wl)
			wl_output_destroy(o->wl);
	}
	if (st->vp)
		zwlr_virtual_pointer_v1_destroy(st->vp);
	if (st->xdg_output_manager)
		zxdg_output_manager_v1_destroy(st->xdg_output_manager);
	if (st->xkb_state)
		xkb_state_unref(st->xkb_state);
	if (st->xkb_map)
		xkb_keymap_unref(st->xkb_map);
	if (st->xkb_ctx)
		xkb_context_unref(st->xkb_ctx);
	if (st->display) {
		wl_display_flush(st->display);
		wl_display_disconnect(st->display);
	}
	free(st);
	p->priv = NULL;
}

/* ---- init ------------------------------------------------------------ */

struct platform *platform_init(void)
{
	struct wl_state *st = calloc(1, sizeof *st);
	if (!st)
		return NULL;
	st->alive = 1;

	st->display = wl_display_connect(NULL);
	if (!st->display) {
		fprintf(stderr, "mouseboard: cannot connect to Wayland "
				"(is WAYLAND_DISPLAY set?)\n");
		free(st);
		return NULL;
	}

	/* Build the xkb context before the first roundtrip: the seat's
	 * wl_keyboard.keymap can be delivered during any roundtrip below (the
	 * xdg-output one in particular), and kb_keymap hands it straight to xkb,
	 * which dereferences the context. Creating it later crashes at launch.
	 *
	 * NO_DEFAULT_INCLUDES: only the fully expanded keymap the compositor
	 * sends is ever compiled (xkb_keymap_new_from_buffer), never one resolved
	 * by name, so no XKB data directory is needed. Without this the static
	 * build fails at startup: its bundled xkbcommon has a default include
	 * path baked to the build sysroot, which does not exist at runtime. */
	st->xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_DEFAULT_INCLUDES);
	if (!st->xkb_ctx) {
		fprintf(stderr, "mouseboard: xkb_context_new failed\n");
		wl_display_disconnect(st->display);
		free(st);
		return NULL;
	}

	struct wl_registry *reg = wl_display_get_registry(st->display);
	wl_registry_add_listener(reg, &reg_listener, st);
	wl_display_roundtrip(st->display); /* globals + output binds */
	wl_display_roundtrip(st->display); /* output geometry, seat caps */

	const char *missing = NULL;
	if (!st->compositor)
		missing = "wl_compositor";
	else if (!st->shm)
		missing = "wl_shm";
	else if (!st->seat)
		missing = "wl_seat";
	else if (!st->layer_shell)
		missing = "zwlr_layer_shell_v1";
	else if (!st->vp_manager)
		missing = "zwlr_virtual_pointer_manager_v1";
	else if (st->n_outputs == 0)
		missing = "wl_output";
	if (missing) {
		fprintf(stderr,
			"mouseboard: compositor lacks %s; a wlroots-based "
			"compositor (sway, Hyprland, river, ...) is required\n",
			missing);
		wl_display_disconnect(st->display);
		free(st);
		return NULL;
	}

	/* Optional: authoritative logical geometry via xdg-output. */
	if (st->xdg_output_manager) {
		for (int s = 0; s < st->n_outputs; s++) {
			struct output *o = &st->outputs[s];
			o->xdg = zxdg_output_manager_v1_get_xdg_output(
				st->xdg_output_manager, o->wl);
			zxdg_output_v1_add_listener(o->xdg,
						    &xdg_output_listener, o);
		}
		wl_display_roundtrip(st->display);
	}

	for (int s = 0; s < st->n_outputs; s++) {
		struct output *o = &st->outputs[s];
		o->surface = wl_compositor_create_surface(st->compositor);
		o->layer = zwlr_layer_shell_v1_get_layer_surface(
			st->layer_shell, o->surface, o->wl,
			ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "mouseboard");
		zwlr_layer_surface_v1_add_listener(o->layer, &ls_listener, o);
		zwlr_layer_surface_v1_set_anchor(
			o->layer,
			ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
				ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
				ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
				ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
		zwlr_layer_surface_v1_set_exclusive_zone(o->layer, -1);
		zwlr_layer_surface_v1_set_keyboard_interactivity(
			o->layer,
			ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
		zwlr_layer_surface_v1_set_size(o->layer, 0, 0);

		/* Empty input region: the overlay grabs the keyboard but lets
		 * pointer events fall through, so the synthesized click reaches
		 * the window underneath rather than the overlay itself. */
		struct wl_region *empty =
			wl_compositor_create_region(st->compositor);
		wl_surface_set_input_region(o->surface, empty);
		wl_region_destroy(empty);

		wl_surface_commit(o->surface);
	}
	/* One device covers the complete layout, so a cross-output drag sends its
	 * press, motion, and release through the same virtual pointer. */
	st->vp = zwlr_virtual_pointer_manager_v1_create_virtual_pointer(
		st->vp_manager, st->seat);

	while (st->alive && st->configured_count < st->n_outputs) {
		if (wl_display_dispatch(st->display) < 0) {
			fprintf(stderr, "mouseboard: display error\n");
			wl_display_disconnect(st->display);
			free(st);
			return NULL;
		}
	}
	if (!st->alive) {
		if (!st->failed)
			fprintf(stderr, "mouseboard: overlay closed during setup\n");
		wl_display_disconnect(st->display);
		free(st);
		return NULL;
	}
	wl_display_roundtrip(st->display); /* settle keymap/modifiers */
	if (!st->keyboard || !st->xkb_state) {
		fprintf(stderr, "mouseboard: no usable Wayland keyboard\n");
		wl_display_disconnect(st->display);
		free(st);
		return NULL;
	}

	int64_t min_x = st->outputs[0].x, min_y = st->outputs[0].y;
	int64_t max_x = min_x + st->outputs[0].width;
	int64_t max_y = min_y + st->outputs[0].height;
	for (int s = 1; s < st->n_outputs; s++) {
		struct output *o = &st->outputs[s];
		if (o->x < min_x)
			min_x = o->x;
		if (o->y < min_y)
			min_y = o->y;
		if ((int64_t)o->x + o->width > max_x)
			max_x = (int64_t)o->x + o->width;
		if ((int64_t)o->y + o->height > max_y)
			max_y = (int64_t)o->y + o->height;
	}
	if (max_x > INT_MAX || max_y > INT_MAX || max_x - min_x > INT_MAX ||
	    max_y - min_y > INT_MAX) {
		fprintf(stderr, "mouseboard: output layout is too large\n");
		wl_display_disconnect(st->display);
		free(st);
		return NULL;
	}
	st->bx = (int)min_x;
	st->by = (int)min_y;
	st->bw = (int)(max_x - min_x);
	st->bh = (int)(max_y - min_y);

	g_platform.priv = st;
	g_platform.n_screens = st->n_outputs;
	for (int s = 0; s < st->n_outputs; s++) {
		struct output *o = &st->outputs[s];
		g_platform.screens[s] = (struct mb_screen){ o->x, o->y,
							    o->width, o->height };
	}
	st->ready = 1;
	g_platform.frame_begin = wl_frame_begin;
	g_platform.frame_commit = wl_frame_commit;
	g_platform.next_key = wl_next_key;
	g_platform.pointer_move = wl_pointer_move;
	g_platform.pointer_button = wl_pointer_button;
	g_platform.teardown = wl_teardown;
	return &g_platform;
}
