diff options
| author | Lena <lena@omega> | 2026-01-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-01-01 00:00:00 +0000 |
| commit | 2fa72ee0dc67d973330aec4ba98260e9f57f7821 (patch) | |
| tree | e6a25c07c8da61d29e4e1f8a72436565e63c5777 /backend-wayland.c | |
| download | mouseboard-master.tar.gz | |
Keyboard-only pointing: a labelled grid over every monitor, type a
label to jump to a cell, bisect it by quadrant until the crosshair is
exact, then one key clicks. The coarse grid keeps labels short and
bisection keeps precision unlimited, so a hotkey and a few keystrokes
replace the mouse.
The core talks only to the backend interface in platform.h; wlroots
Wayland and Win32 backends supply the overlay, keyboard grab and
pointer.
Diffstat (limited to 'backend-wayland.c')
| -rw-r--r-- | backend-wayland.c | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/backend-wayland.c b/backend-wayland.c new file mode 100644 index 0000000..041c648 --- /dev/null +++ b/backend-wayland.c @@ -0,0 +1,854 @@ +/* + * 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, ®_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; +} |