/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #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; }