summaryrefslogtreecommitdiff
path: root/src/wayland/pointer.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-11 21:53:42 +0300
commit927ae621c9a3d46dbc401b8e9ea7bbed1bf2e97b (patch)
treeaeb00c16770dcf5a36d28c1167cc39c6759059fb /src/wayland/pointer.c
initial: Yet Another Surface Library (YASL) 0.1.0
A minimal, modern desktop window surface library in C. Supports Wayland on Linux and Cocoa/Metal on macOS with presentation timing, fractional scaling, IME, and CSD.
Diffstat (limited to 'src/wayland/pointer.c')
-rw-r--r--src/wayland/pointer.c1291
1 files changed, 1291 insertions, 0 deletions
diff --git a/src/wayland/pointer.c b/src/wayland/pointer.c
new file mode 100644
index 0000000..37e72f3
--- /dev/null
+++ b/src/wayland/pointer.c
@@ -0,0 +1,1291 @@
+#include "internal.h"
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define SCROLL_PX_PER_LINE 10.0
+
+struct tablet_tool {
+ struct tablet_tool *next;
+ struct seat *seat;
+ struct zwp_tablet_tool_v2 *proxy;
+ struct wp_cursor_shape_device_v1 *shape_dev;
+ uint32_t proximity_serial;
+ bool eraser;
+};
+
+struct tablet_device {
+ struct tablet_device *next;
+ struct zwp_tablet_v2 *proxy;
+};
+
+struct tablet_pad {
+ struct tablet_pad *next;
+ struct zwp_tablet_pad_v2 *proxy;
+};
+
+static int device_px_round(double v)
+{
+ return (int)(v < 0 ? v - 0.5 : v + 0.5);
+}
+
+static struct yasl_vector2i device_from_fixed(const struct yasl_surface *w,
+ wl_fixed_t x, wl_fixed_t y)
+{
+ return (struct yasl_vector2i){
+ .x = device_px_round(wl_fixed_to_double(x) * w->scale_factor),
+ .y = device_px_round(wl_fixed_to_double(y) * w->scale_factor),
+ };
+}
+
+static int motion_button(uint64_t buttons)
+{
+ for (int b = 0; b <= 7; b++)
+ if (buttons & (1ull << b))
+ return b;
+ return 3;
+}
+
+void seat_note_input(struct seat *s, uint32_t serial)
+{
+ s->input_serial = serial;
+ s->conn->active_seat = s;
+}
+
+struct seat *conn_active_seat(const struct yasl_display *c)
+{
+ return c->active_seat ? c->active_seat : c->seats;
+}
+
+static void push_mouse(struct seat *s, struct yasl_surface *w, struct yasl_mouse_event ev)
+{
+ ev.mods = (unsigned)seat_modifiers(s);
+ struct yasl_input_event input_ev = {
+ .kind = YASL_INPUT_MOUSE,
+ .surface = w,
+ .mouse = ev,
+ };
+ input_queue_push(&surface_root(w)->input, &input_ev);
+}
+
+static void report_mouse(struct seat *s, struct yasl_surface *w, struct yasl_vector2i pos,
+ int button, enum yasl_pointer_kind kind)
+{
+ push_mouse(s, w, (struct yasl_mouse_event){
+ .pos = pos, .button = button, .kind = kind,
+ });
+}
+
+static void report_wheel(struct seat *s, struct yasl_surface *w, struct yasl_vector2i pos,
+ int button, int lines, float delta)
+{
+ push_mouse(s, w, (struct yasl_mouse_event){
+ .pos = pos, .button = button, .kind = YASL_PTR_SCROLL,
+ .scroll_lines = lines, .scroll_delta = delta,
+ });
+}
+
+static bool cursor_theme_for(struct yasl_display *c, int scale)
+{
+ if (c->cursor_theme && c->cursor_theme_scale == scale)
+ return true;
+ struct wl_cursor_theme *theme = wl_cursor_theme_load(c->cursor_theme_name,
+ c->cursor_size * scale, c->shm);
+ if (!theme)
+ return c->cursor_theme != NULL;
+ if (c->cursor_theme)
+ wl_cursor_theme_destroy(c->cursor_theme);
+ c->cursor_theme = theme;
+ c->cursor_theme_scale = scale;
+ return true;
+}
+
+static bool seat_cursor_surface(struct seat *s)
+{
+ struct yasl_display *c = s->conn;
+ if (s->cursor_surface)
+ return true;
+ s->cursor_surface = wl_compositor_create_surface(c->compositor);
+ if (!s->cursor_surface)
+ return false;
+ if (c->viewporter)
+ s->cursor_viewport = wp_viewporter_get_viewport(c->viewporter, s->cursor_surface);
+ return true;
+}
+
+static void set_cursor_theme_fallback(struct seat *s, struct yasl_surface *w,
+ enum yasl_cursor shape)
+{
+ struct yasl_display *c = s->conn;
+ int scale = (int)((w->scale_120 + YASL_SCALE_UNIT - 1) / YASL_SCALE_UNIT);
+ if (scale < 1) scale = 1;
+ if (!seat_cursor_surface(s) || !cursor_theme_for(c, scale)) return;
+ static const char *names[][2] = {
+ {"default", "left_ptr"},
+ {"text", "xterm"},
+ {"pointer", "hand2"},
+ {"crosshair", "crosshair"},
+ {"move", "move"},
+ {"nwse-resize", "nw-resize"},
+ {"nesw-resize", "ne-resize"},
+ {"ew-resize", "ew-resize"},
+ {"ns-resize", "ns-resize"},
+ };
+ int idx = shape < YASL_CURSOR_NONE ? (int)shape : 0;
+ struct wl_cursor *cursor = wl_cursor_theme_get_cursor(c->cursor_theme, names[idx][0]);
+ if (!cursor) cursor = wl_cursor_theme_get_cursor(c->cursor_theme, names[idx][1]);
+ if (!cursor || !cursor->image_count) return;
+ struct wl_cursor_image *img = cursor->images[0];
+ struct wl_buffer *buffer = wl_cursor_image_get_buffer(img);
+ if (!buffer) return;
+
+ int theme_scale = c->cursor_theme_scale;
+ if (s->cursor_viewport) {
+ wp_viewport_set_destination(s->cursor_viewport,
+ logical_px_ceil((int)img->width, (uint32_t)theme_scale * YASL_SCALE_UNIT),
+ logical_px_ceil((int)img->height, (uint32_t)theme_scale * YASL_SCALE_UNIT));
+ } else {
+ if (img->width % (uint32_t)theme_scale || img->height % (uint32_t)theme_scale)
+ theme_scale = 1;
+ wl_surface_set_buffer_scale(s->cursor_surface, theme_scale);
+ }
+ int shown = s->cursor_viewport ? c->cursor_theme_scale : theme_scale;
+ wl_pointer_set_cursor(s->pointer, s->pointer_serial, s->cursor_surface,
+ (int32_t)img->hotspot_x / shown, (int32_t)img->hotspot_y / shown);
+ wl_surface_attach(s->cursor_surface, buffer, 0, 0);
+ wl_surface_damage_buffer(s->cursor_surface, 0, 0,
+ (int32_t)img->width, (int32_t)img->height);
+ wl_surface_commit(s->cursor_surface);
+}
+
+static uint32_t cursor_shape_of(enum yasl_cursor shape)
+{
+ switch (shape) {
+ case YASL_CURSOR_TEXT: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_TEXT;
+ case YASL_CURSOR_LINK: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_POINTER;
+ case YASL_CURSOR_CROSSHAIR: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
+ case YASL_CURSOR_MOVE: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE;
+ case YASL_CURSOR_RESIZE_EW: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_EW_RESIZE;
+ case YASL_CURSOR_RESIZE_NS: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NS_RESIZE;
+ case YASL_CURSOR_RESIZE_NWSE: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NWSE_RESIZE;
+ case YASL_CURSOR_RESIZE_NESW: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NESW_RESIZE;
+ default: return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
+ }
+}
+
+static void seat_set_cursor(struct seat *s, struct yasl_surface *w, enum yasl_cursor shape)
+{
+ if (!s->pointer || !s->pointer_serial || s->pointer_focus != w ||
+ s->cursor_shape == (int)shape)
+ return;
+ if (shape == YASL_CURSOR_NONE)
+ wl_pointer_set_cursor(s->pointer, s->pointer_serial, NULL, 0, 0);
+ else if (s->cursor_shape_dev)
+ wp_cursor_shape_device_v1_set_shape(s->cursor_shape_dev,
+ s->pointer_serial, cursor_shape_of(shape));
+ else
+ set_cursor_theme_fallback(s, w, shape);
+ s->cursor_shape = shape;
+}
+
+static void tool_set_cursor(struct tablet_tool *t, enum yasl_cursor shape)
+{
+ if (!t->proximity_serial)
+ return;
+ if (shape == YASL_CURSOR_NONE)
+ zwp_tablet_tool_v2_set_cursor(t->proxy, t->proximity_serial, NULL, 0, 0);
+ else if (t->shape_dev)
+ wp_cursor_shape_device_v1_set_shape(t->shape_dev, t->proximity_serial,
+ cursor_shape_of(shape));
+}
+
+void yasl_surface_set_cursor(struct yasl_surface *w, enum yasl_cursor shape)
+{
+ if (!w || !w->conn) return;
+ for (struct seat *s = w->conn->seats; s; s = s->next) {
+ seat_set_cursor(s, w, shape);
+ if (s->tool && s->tablet_focus == w)
+ tool_set_cursor(s->tool, shape);
+ }
+}
+
+static void press_queue_flush(struct seat *s, struct press_queue *q,
+ struct yasl_surface *only, uint64_t *buttons,
+ struct yasl_mouse_event like)
+{
+ int kept = 0;
+ for (int i = 0; i < q->n; i++) {
+ struct pending_press p = q->ev[i];
+ if (only && p.surface != only) {
+ q->ev[kept++] = p;
+ continue;
+ }
+ if (!p.surface) continue;
+ if (p.release) *buttons &= ~(1ull << p.button);
+ else *buttons |= 1ull << p.button;
+ struct yasl_mouse_event ev = like;
+ ev.pos = p.pos;
+ ev.button = p.button;
+ ev.kind = p.release ? YASL_PTR_RELEASE : YASL_PTR_PRESS;
+ push_mouse(s, p.surface, ev);
+ }
+ q->n = kept;
+}
+
+static void press_queue_push(struct press_queue *q, struct yasl_surface *w,
+ struct yasl_vector2i pos, int button, bool release)
+{
+ if (q->n == (int)(sizeof(q->ev) / sizeof(q->ev[0]))) {
+ memmove(q->ev, q->ev + 1, sizeof(q->ev[0]) * (size_t)(q->n - 1));
+ q->n--;
+ }
+ q->ev[q->n++] = (struct pending_press){
+ .surface = w, .pos = pos, .button = button, .release = release,
+ };
+}
+
+static void press_queue_forget(struct press_queue *q, struct yasl_surface *w)
+{
+ for (int i = 0; i < q->n; i++)
+ if (q->ev[i].surface == w)
+ q->ev[i].surface = NULL;
+}
+
+void seat_pointer_reset(struct seat *s)
+{
+ s->pointer_buttons = 0;
+ s->pointer_serial = 0;
+ s->motion_pending = false;
+ s->axis_v = s->axis_h = s->frame_v = 0;
+ s->frame_v120 = 0;
+ s->axis_discrete_v = s->axis_discrete_h = 0;
+ s->axis_value120_v = s->axis_value120_h = 0;
+ s->axis_has_v120_v = s->axis_has_v120_h = false;
+ s->axis_smooth = false;
+ s->cursor_shape = -1;
+}
+
+static void pointer_frame(void *data, struct wl_pointer *pointer);
+
+static void pointer_event_done(struct seat *s)
+{
+ if (s->pointer && wl_pointer_get_version(s->pointer) < WL_POINTER_FRAME_SINCE_VERSION)
+ pointer_frame(s, s->pointer);
+}
+
+static void flush_pending_motion(struct seat *s, struct yasl_surface *w)
+{
+ if (!s->motion_pending) return;
+ s->motion_pending = false;
+ report_mouse(s, w, s->pointer_pos, motion_button(s->pointer_buttons), YASL_PTR_MOTION);
+}
+
+static void pointer_enter(void *data, struct wl_pointer *pointer,
+ uint32_t serial, struct wl_surface *surface,
+ wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+ (void)pointer;
+ struct seat *s = data;
+ struct yasl_surface *w = conn_surface_for(s->conn, surface);
+ s->pointer_focus = w;
+ if (!w) return;
+ s->pointer_pos = device_from_fixed(w, surface_x, surface_y);
+ s->pointer_serial = serial;
+ s->cursor_shape = -1;
+ seat_set_cursor(s, w, w->parent ? YASL_CURSOR_ARROW : YASL_CURSOR_TEXT);
+ s->motion_pending = true;
+ pointer_event_done(s);
+}
+
+static void pointer_leave(void *data, struct wl_pointer *pointer,
+ uint32_t serial, struct wl_surface *surface)
+{
+ (void)pointer; (void)serial; (void)surface;
+ struct seat *s = data;
+ struct yasl_surface *w = s->pointer_focus;
+ s->pointer_focus = NULL;
+ if (!w) return;
+ if (s->ptr_left)
+ pointer_frame(s, s->pointer);
+ s->ptr_left = w;
+ s->ptr_left_pos = s->pointer_pos;
+ s->ptr_left_motion = s->motion_pending;
+ s->motion_pending = false;
+ pointer_event_done(s);
+}
+
+static void pointer_motion(void *data, struct wl_pointer *pointer,
+ uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y)
+{
+ (void)pointer; (void)time;
+ struct seat *s = data;
+ struct yasl_surface *w = s->pointer_focus;
+ if (!w) return;
+ s->pointer_pos = device_from_fixed(w, surface_x, surface_y);
+ s->motion_pending = true;
+ pointer_event_done(s);
+}
+
+static void pointer_button(void *data, struct wl_pointer *pointer,
+ uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
+{
+ (void)pointer; (void)time;
+ struct seat *s = data;
+ struct yasl_surface *w = s->pointer_focus;
+ if (!w) return;
+ int b;
+ switch (button) {
+ case BTN_LEFT: b = 0; break;
+ case BTN_MIDDLE: b = 1; break;
+ case BTN_RIGHT: b = 2; break;
+ case BTN_BACK: b = 3; break;
+ case BTN_FORWARD: b = 4; break;
+ default: return;
+ }
+ seat_note_input(s, serial);
+ bool release = state == WL_POINTER_BUTTON_STATE_RELEASED;
+ if (!release) {
+ s->press_serial = serial;
+ s->press_surface = w;
+ }
+ press_queue_push(&s->ptr_presses, w, s->pointer_pos, b, release);
+ pointer_event_done(s);
+}
+
+static void pointer_axis(void *data, struct wl_pointer *pointer,
+ uint32_t time, uint32_t axis, wl_fixed_t value)
+{
+ (void)pointer; (void)time;
+ struct seat *s = data;
+ if (!s->pointer_focus) return;
+ double v = wl_fixed_to_double(value);
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ s->axis_v += v;
+ s->frame_v += v;
+ } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
+ s->axis_h += v;
+ }
+ pointer_event_done(s);
+}
+
+static void pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
+{
+ (void)pointer;
+ struct seat *s = data;
+ if (axis_source == WL_POINTER_AXIS_SOURCE_FINGER ||
+ axis_source == WL_POINTER_AXIS_SOURCE_CONTINUOUS)
+ s->axis_smooth = true;
+}
+
+static void pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
+{
+ (void)pointer; (void)time;
+ struct seat *s = data;
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ s->axis_value120_v = 0;
+ s->axis_has_v120_v = false;
+ } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
+ s->axis_value120_h = 0;
+ s->axis_has_v120_h = false;
+ }
+}
+
+static void pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete)
+{
+ (void)pointer;
+ struct seat *s = data;
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
+ s->axis_discrete_v += discrete;
+ else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
+ s->axis_discrete_h += discrete;
+}
+
+static void pointer_axis_value120(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t value120)
+{
+ (void)pointer;
+ struct seat *s = data;
+ if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) {
+ s->axis_value120_v += value120;
+ s->frame_v120 += value120;
+ s->axis_has_v120_v = true;
+ } else if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) {
+ s->axis_value120_h += value120;
+ s->axis_has_v120_h = true;
+ }
+}
+
+static void pointer_frame_vertical(struct seat *s, struct yasl_surface *w)
+{
+ if (s->axis_has_v120_v) {
+ int discrete = s->axis_value120_v / 120;
+ s->axis_value120_v -= discrete * 120;
+ float delta = -(float)s->frame_v120 / 120.0f;
+ int btn = discrete ? (discrete < 0 ? 4 : 5) : (delta > 0 ? 4 : 5);
+ if (discrete || delta != 0.0f)
+ report_wheel(s, w, s->pointer_pos, btn, -discrete, delta);
+ s->axis_v = 0;
+ } else if (s->axis_discrete_v) {
+ report_wheel(s, w, s->pointer_pos, s->axis_discrete_v > 0 ? 5 : 4, -s->axis_discrete_v, 0.0f);
+ s->axis_v = 0;
+ } else if (s->frame_v != 0.0) {
+ int lines = -(int)(s->axis_v / SCROLL_PX_PER_LINE);
+ s->axis_v += (double)lines * SCROLL_PX_PER_LINE;
+ float delta = (float)(-s->frame_v / SCROLL_PX_PER_LINE);
+ report_wheel(s, w, s->pointer_pos, delta > 0 ? 4 : 5, lines, delta);
+ }
+ s->frame_v = 0;
+ s->frame_v120 = 0;
+}
+
+static void pointer_frame_horizontal(struct seat *s, struct yasl_surface *w)
+{
+ if (s->axis_has_v120_h) {
+ int discrete = s->axis_value120_h / 120;
+ s->axis_value120_h -= discrete * 120;
+ if (discrete)
+ report_wheel(s, w, s->pointer_pos, discrete > 0 ? 7 : 6, discrete, 0.0f);
+ s->axis_h = 0;
+ } else if (s->axis_discrete_h) {
+ report_wheel(s, w, s->pointer_pos, s->axis_discrete_h > 0 ? 7 : 6, s->axis_discrete_h, 0.0f);
+ s->axis_h = 0;
+ } else if (s->axis_h) {
+ int steps = (int)(s->axis_h / SCROLL_PX_PER_LINE);
+ if (steps != 0) {
+ s->axis_h -= (double)steps * SCROLL_PX_PER_LINE;
+ report_wheel(s, w, s->pointer_pos, steps > 0 ? 7 : 6, steps, 0.0f);
+ }
+ }
+}
+
+static void pointer_frame(void *data, struct wl_pointer *pointer)
+{
+ (void)pointer;
+ struct seat *s = data;
+ struct yasl_surface *left = s->ptr_left;
+ if (left) {
+ s->ptr_left = NULL;
+ if (s->ptr_left_motion)
+ report_mouse(s, left, s->ptr_left_pos, motion_button(s->pointer_buttons), YASL_PTR_MOTION);
+ press_queue_flush(s, &s->ptr_presses, left, &s->pointer_buttons, (struct yasl_mouse_event){0});
+ report_mouse(s, left, s->ptr_left_pos, 3, YASL_PTR_LEAVE);
+ struct yasl_vector2i pos = s->pointer_pos;
+ bool motion = s->motion_pending;
+ uint32_t serial = s->pointer_focus ? s->pointer_serial : 0;
+ int shape = s->pointer_focus ? s->cursor_shape : -1;
+ seat_pointer_reset(s);
+ s->pointer_pos = pos;
+ s->motion_pending = motion;
+ s->pointer_serial = serial;
+ s->cursor_shape = shape;
+ }
+ struct yasl_surface *w = s->pointer_focus;
+ if (!w) {
+ s->ptr_presses.n = 0;
+ return;
+ }
+
+ flush_pending_motion(s, w);
+ press_queue_flush(s, &s->ptr_presses, NULL, &s->pointer_buttons, (struct yasl_mouse_event){0});
+
+ if (s->axis_smooth) {
+ if (!s->axis_has_v120_v) s->axis_value120_v = 0;
+ if (!s->axis_has_v120_h) s->axis_value120_h = 0;
+ s->axis_smooth = false;
+ }
+ pointer_frame_vertical(s, w);
+ pointer_frame_horizontal(s, w);
+
+ s->axis_discrete_v = 0;
+ s->axis_discrete_h = 0;
+ s->axis_has_v120_v = false;
+ s->axis_has_v120_h = false;
+}
+
+static void pointer_axis_relative_direction(void *data, struct wl_pointer *pointer, uint32_t axis, uint32_t direction)
+{ (void)data; (void)pointer; (void)axis; (void)direction; }
+static void pointer_warp(void *data, struct wl_pointer *pointer, wl_fixed_t x, wl_fixed_t y)
+{ (void)data; (void)pointer; (void)x; (void)y; }
+
+static const struct wl_pointer_listener pointer_listener = {
+ .axis_relative_direction = pointer_axis_relative_direction,
+ .warp = pointer_warp,
+ .enter = pointer_enter,
+ .leave = pointer_leave,
+ .motion = pointer_motion,
+ .button = pointer_button,
+ .axis = pointer_axis,
+ .frame = pointer_frame,
+ .axis_source = pointer_axis_source,
+ .axis_stop = pointer_axis_stop,
+ .axis_discrete = pointer_axis_discrete,
+ .axis_value120 = pointer_axis_value120,
+};
+
+static struct touch_point *touch_find(struct seat *s, int32_t id)
+{
+ for (int i = 0; i < SEAT_TOUCH_POINTS; i++)
+ if (s->touch_points[i].active && s->touch_points[i].id == id)
+ return &s->touch_points[i];
+ return NULL;
+}
+
+static struct yasl_vector2i touch_pos(const struct touch_point *p)
+{
+ double scale = p->surface->scale_factor;
+ return (struct yasl_vector2i){
+ .x = device_px_round(p->x * scale), .y = device_px_round(p->y * scale),
+ };
+}
+
+static int touch_count(const struct seat *s)
+{
+ int n = 0;
+ for (int i = 0; i < SEAT_TOUCH_POINTS; i++)
+ if (s->touch_points[i].active && !s->touch_points[i].up)
+ n++;
+ return n;
+}
+
+static double touch_centroid_y(const struct seat *s, const struct yasl_surface *on)
+{
+ double sum = 0;
+ int n = 0;
+ for (int i = 0; i < SEAT_TOUCH_POINTS; i++) {
+ const struct touch_point *p = &s->touch_points[i];
+ if (p->active && !p->up && p->surface == on) {
+ sum += p->y;
+ n++;
+ }
+ }
+ return n ? sum / n : 0;
+}
+
+static void touch_report(struct seat *s, struct touch_point *p, int button,
+ enum yasl_pointer_kind kind)
+{
+ push_mouse(s, p->surface, (struct yasl_mouse_event){
+ .pos = touch_pos(p), .button = button, .kind = kind,
+ .device = YASL_PTR_DEV_TOUCH,
+ });
+}
+
+static void touch_down(void *data, struct wl_touch *touch,
+ uint32_t serial, uint32_t time, struct wl_surface *surface,
+ int32_t id, wl_fixed_t x, wl_fixed_t y)
+{
+ (void)touch; (void)time;
+ struct seat *s = data;
+ struct yasl_surface *w = conn_surface_for(s->conn, surface);
+ if (!w || touch_find(s, id)) return;
+ struct touch_point *p = NULL;
+ for (int i = 0; i < SEAT_TOUCH_POINTS && !p; i++)
+ if (!s->touch_points[i].active)
+ p = &s->touch_points[i];
+ if (!p) return;
+ seat_note_input(s, serial);
+ s->press_serial = serial;
+ s->press_surface = w;
+ *p = (struct touch_point){
+ .surface = w, .id = id, .active = true, .down = true,
+ .x = wl_fixed_to_double(x), .y = wl_fixed_to_double(y),
+ };
+}
+
+static void touch_up(void *data, struct wl_touch *touch, uint32_t serial, uint32_t time, int32_t id)
+{
+ (void)touch; (void)serial; (void)time;
+ struct touch_point *p = touch_find(data, id);
+ if (p) p->up = true;
+}
+
+static void touch_motion(void *data, struct wl_touch *touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y)
+{
+ (void)touch; (void)time;
+ struct touch_point *p = touch_find(data, id);
+ if (!p) return;
+ p->x = wl_fixed_to_double(x);
+ p->y = wl_fixed_to_double(y);
+ p->moved = true;
+}
+
+static void touch_frame(void *data, struct wl_touch *touch)
+{
+ (void)touch;
+ struct seat *s = data;
+ for (int i = 0; i < SEAT_TOUCH_POINTS; i++) {
+ struct touch_point *p = &s->touch_points[i];
+ if (p->active && p->down && !s->touch_primary && !s->touch_scrolling) {
+ s->touch_primary = p;
+ s->touch_pressed = true;
+ touch_report(s, p, 0, YASL_PTR_PRESS);
+ }
+ }
+ struct touch_point *primary = s->touch_primary;
+ if (primary && !s->touch_scrolling && touch_count(s) >= 2) {
+ if (s->touch_pressed)
+ touch_report(s, primary, 0, YASL_PTR_RELEASE);
+ s->touch_pressed = false;
+ s->touch_scrolling = true;
+ s->touch_scroll_y = touch_centroid_y(s, primary->surface);
+ s->touch_scroll_rest = 0;
+ } else if (s->touch_scrolling && primary && touch_count(s) >= 2) {
+ double y = touch_centroid_y(s, primary->surface);
+ double dy = y - s->touch_scroll_y;
+ s->touch_scroll_y = y;
+ if (dy != 0.0) {
+ double lines = dy / SCROLL_PX_PER_LINE;
+ s->touch_scroll_rest += lines;
+ int whole = (int)s->touch_scroll_rest;
+ s->touch_scroll_rest -= whole;
+ push_mouse(s, primary->surface, (struct yasl_mouse_event){
+ .pos = touch_pos(primary), .button = lines > 0 ? 4 : 5,
+ .kind = YASL_PTR_SCROLL, .scroll_lines = whole,
+ .scroll_delta = (float)lines, .device = YASL_PTR_DEV_TOUCH,
+ });
+ }
+ } else if (primary && primary->moved && s->touch_pressed) {
+ touch_report(s, primary, 0, YASL_PTR_MOTION);
+ }
+ for (int i = 0; i < SEAT_TOUCH_POINTS; i++) {
+ struct touch_point *p = &s->touch_points[i];
+ if (!p->active) continue;
+ if (p->up) {
+ if (p == s->touch_primary) {
+ if (s->touch_pressed)
+ touch_report(s, p, 0, YASL_PTR_RELEASE);
+ s->touch_pressed = false;
+ s->touch_primary = NULL;
+ }
+ *p = (struct touch_point){0};
+ } else {
+ p->down = p->moved = false;
+ }
+ }
+ if (!touch_count(s))
+ s->touch_scrolling = false;
+}
+
+static void touch_cancel(void *data, struct wl_touch *touch)
+{
+ (void)touch;
+ struct seat *s = data;
+ if (s->touch_primary && s->touch_pressed)
+ touch_report(s, s->touch_primary, 0, YASL_PTR_RELEASE);
+ memset(s->touch_points, 0, sizeof(s->touch_points));
+ s->touch_primary = NULL;
+ s->touch_pressed = false;
+ s->touch_scrolling = false;
+}
+
+static void touch_shape(void *data, struct wl_touch *touch, int32_t id, wl_fixed_t major, wl_fixed_t minor)
+{ (void)data; (void)touch; (void)id; (void)major; (void)minor; }
+static void touch_orientation(void *data, struct wl_touch *touch, int32_t id, wl_fixed_t orientation)
+{ (void)data; (void)touch; (void)id; (void)orientation; }
+
+static const struct wl_touch_listener touch_listener = {
+ .shape = touch_shape, .orientation = touch_orientation,
+ .down = touch_down, .up = touch_up, .motion = touch_motion,
+ .frame = touch_frame, .cancel = touch_cancel,
+};
+
+static struct yasl_mouse_event tool_event(const struct seat *s)
+{
+ return (struct yasl_mouse_event){
+ .device = s->tool && s->tool->eraser ? YASL_PTR_DEV_ERASER : YASL_PTR_DEV_PEN,
+ .pen = s->pen,
+ };
+}
+
+static void tablet_tool_proximity_in(void *data,
+ struct zwp_tablet_tool_v2 *tool, uint32_t serial,
+ struct zwp_tablet_v2 *tablet, struct wl_surface *surface)
+{
+ (void)tool; (void)tablet;
+ struct tablet_tool *t = data;
+ struct seat *s = t->seat;
+ struct yasl_surface *w = conn_surface_for(s->conn, surface);
+ s->tablet_focus = w;
+ s->tool = t;
+ s->tool_buttons = 0;
+ s->pen = (struct yasl_pointer_pen){0};
+ t->proximity_serial = serial;
+ if (!w) return;
+ if (s->conn->cursor_shape_mgr && !t->shape_dev)
+ t->shape_dev = wp_cursor_shape_manager_v1_get_tablet_tool_v2(
+ s->conn->cursor_shape_mgr, t->proxy);
+ tool_set_cursor(t, w->parent ? YASL_CURSOR_ARROW : YASL_CURSOR_TEXT);
+ s->tool_motion = true;
+}
+
+static void tablet_tool_proximity_out(void *data, struct zwp_tablet_tool_v2 *tool)
+{
+ (void)tool;
+ struct tablet_tool *t = data;
+ struct seat *s = t->seat;
+ struct yasl_surface *w = s->tablet_focus;
+ if (w) {
+ press_queue_flush(s, &s->tool_presses, NULL, &s->tool_buttons, tool_event(s));
+ struct yasl_mouse_event ev = tool_event(s);
+ ev.pos = s->tool_pos;
+ ev.button = 3;
+ ev.kind = YASL_PTR_LEAVE;
+ push_mouse(s, w, ev);
+ }
+ s->tool_presses.n = 0;
+ s->tablet_focus = NULL;
+ s->tool = NULL;
+ s->tool_motion = false;
+ t->proximity_serial = 0;
+}
+
+static void tablet_tool_down(void *data, struct zwp_tablet_tool_v2 *tool, uint32_t serial)
+{
+ (void)tool;
+ struct seat *s = ((struct tablet_tool *)data)->seat;
+ struct yasl_surface *w = s->tablet_focus;
+ if (!w) return;
+ seat_note_input(s, serial);
+ s->press_serial = serial;
+ s->press_surface = w;
+ press_queue_push(&s->tool_presses, w, s->tool_pos, 0, false);
+}
+
+static void tablet_tool_up(void *data, struct zwp_tablet_tool_v2 *tool)
+{
+ (void)tool;
+ struct seat *s = ((struct tablet_tool *)data)->seat;
+ struct yasl_surface *w = s->tablet_focus;
+ if (!w) return;
+ press_queue_push(&s->tool_presses, w, s->tool_pos, 0, true);
+}
+
+static void tablet_tool_motion(void *data, struct zwp_tablet_tool_v2 *tool, wl_fixed_t x, wl_fixed_t y)
+{
+ (void)tool;
+ struct seat *s = ((struct tablet_tool *)data)->seat;
+ struct yasl_surface *w = s->tablet_focus;
+ if (!w) return;
+ s->tool_pos = device_from_fixed(w, x, y);
+ s->tool_motion = true;
+}
+
+static void tablet_tool_button(void *data, struct zwp_tablet_tool_v2 *tool,
+ uint32_t serial, uint32_t button, uint32_t state)
+{
+ (void)tool;
+ struct seat *s = ((struct tablet_tool *)data)->seat;
+ struct yasl_surface *w = s->tablet_focus;
+ if (!w) return;
+ seat_note_input(s, serial);
+ int b;
+ switch (button) {
+ case BTN_STYLUS: b = 2; break;
+ case BTN_STYLUS2: b = 1; break;
+ case BTN_STYLUS3: b = 3; break;
+ default: return;
+ }
+ bool release = state == ZWP_TABLET_TOOL_V2_BUTTON_STATE_RELEASED;
+ press_queue_push(&s->tool_presses, w, s->tool_pos, b, release);
+}
+
+static void tool_pressure(void *d, struct zwp_tablet_tool_v2 *t, uint32_t v)
+{
+ (void)t;
+ struct seat *s = ((struct tablet_tool *)d)->seat;
+ s->pen.pressure = v > 65535 ? 65535 : (uint16_t)v;
+ s->tool_motion = true;
+}
+
+static int8_t tilt_degrees(wl_fixed_t v)
+{
+ double d = wl_fixed_to_double(v);
+ return (int8_t)(d < -90 ? -90 : d > 90 ? 90 : lround(d));
+}
+
+static void tool_tilt(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t x, wl_fixed_t y)
+{
+ (void)t;
+ struct seat *s = ((struct tablet_tool *)d)->seat;
+ s->pen.tilt_x = tilt_degrees(x);
+ s->pen.tilt_y = tilt_degrees(y);
+ s->tool_motion = true;
+}
+
+static void tool_wheel(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t deg, int32_t clicks)
+{
+ (void)t; (void)deg;
+ ((struct tablet_tool *)d)->seat->tool_wheel += clicks;
+}
+
+static void tool_frame(void *d, struct zwp_tablet_tool_v2 *t, uint32_t time)
+{
+ (void)t; (void)time;
+ struct seat *s = ((struct tablet_tool *)d)->seat;
+ struct yasl_surface *w = s->tablet_focus;
+ if (!w) {
+ s->tool_presses.n = 0;
+ s->tool_wheel = 0;
+ return;
+ }
+ struct yasl_mouse_event like = tool_event(s);
+ if (s->tool_motion) {
+ struct yasl_mouse_event ev = like;
+ ev.pos = s->tool_pos;
+ ev.button = motion_button(s->tool_buttons);
+ ev.kind = YASL_PTR_MOTION;
+ push_mouse(s, w, ev);
+ s->tool_motion = false;
+ }
+ press_queue_flush(s, &s->tool_presses, NULL, &s->tool_buttons, like);
+ if (s->tool_wheel) {
+ report_wheel(s, w, s->tool_pos, s->tool_wheel > 0 ? 5 : 4, -s->tool_wheel, 0.0f);
+ s->tool_wheel = 0;
+ }
+}
+
+static void tablet_tool_removed(void *data, struct zwp_tablet_tool_v2 *tool)
+{
+ struct tablet_tool *t = data;
+ struct seat *s = t->seat;
+ if (s->tool == t) {
+ s->tool = NULL;
+ s->tablet_focus = NULL;
+ s->tool_presses.n = 0;
+ }
+ struct tablet_tool **link = &s->tablet_tools;
+ while (*link && *link != t)
+ link = &(*link)->next;
+ if (*link)
+ *link = t->next;
+ if (t->shape_dev)
+ wp_cursor_shape_device_v1_destroy(t->shape_dev);
+ zwp_tablet_tool_v2_destroy(tool);
+ free(t);
+}
+
+static void tool_type(void *d, struct zwp_tablet_tool_v2 *t, uint32_t type)
+{
+ (void)t;
+ ((struct tablet_tool *)d)->eraser = type == ZWP_TABLET_TOOL_V2_TYPE_ERASER;
+}
+
+static void tool_hardware_serial(void *d, struct zwp_tablet_tool_v2 *t, uint32_t hi, uint32_t lo) { (void)d; (void)t; (void)hi; (void)lo; }
+static void tool_hardware_id_wacom(void *d, struct zwp_tablet_tool_v2 *t, uint32_t hi, uint32_t lo) { (void)d; (void)t; (void)hi; (void)lo; }
+static void tool_capability(void *d, struct zwp_tablet_tool_v2 *t, uint32_t cap) { (void)d; (void)t; (void)cap; }
+static void tool_done(void *d, struct zwp_tablet_tool_v2 *t) { (void)d; (void)t; }
+static void tool_distance(void *d, struct zwp_tablet_tool_v2 *t, uint32_t v) { (void)d; (void)t; (void)v; }
+static void tool_rotation(void *d, struct zwp_tablet_tool_v2 *t, wl_fixed_t deg) { (void)d; (void)t; (void)deg; }
+static void tool_slider(void *d, struct zwp_tablet_tool_v2 *t, int32_t pos) { (void)d; (void)t; (void)pos; }
+
+static const struct zwp_tablet_tool_v2_listener tablet_tool_listener = {
+ .type = tool_type, .hardware_serial = tool_hardware_serial,
+ .hardware_id_wacom = tool_hardware_id_wacom, .capability = tool_capability,
+ .done = tool_done, .removed = tablet_tool_removed,
+ .proximity_in = tablet_tool_proximity_in, .proximity_out = tablet_tool_proximity_out,
+ .down = tablet_tool_down, .up = tablet_tool_up, .motion = tablet_tool_motion,
+ .pressure = tool_pressure, .distance = tool_distance, .tilt = tool_tilt,
+ .rotation = tool_rotation, .slider = tool_slider, .wheel = tool_wheel,
+ .button = tablet_tool_button, .frame = tool_frame,
+};
+
+static void tablet_name(void *data, struct zwp_tablet_v2 *tablet, const char *name) { (void)data; (void)tablet; (void)name; }
+static void tablet_id(void *data, struct zwp_tablet_v2 *tablet, uint32_t vid, uint32_t pid) { (void)data; (void)tablet; (void)vid; (void)pid; }
+static void tablet_path(void *data, struct zwp_tablet_v2 *tablet, const char *path) { (void)data; (void)tablet; (void)path; }
+static void tablet_done(void *data, struct zwp_tablet_v2 *tablet) { (void)data; (void)tablet; }
+static void tablet_bustype(void *data, struct zwp_tablet_v2 *tablet, uint32_t bustype) { (void)data; (void)tablet; (void)bustype; }
+
+static void tablet_removed(void *data, struct zwp_tablet_v2 *tablet)
+{
+ struct seat *s = data;
+ struct tablet_device **link = &s->tablet_devices;
+ while (*link && (*link)->proxy != tablet)
+ link = &(*link)->next;
+ if (*link) {
+ struct tablet_device *removed = *link;
+ *link = removed->next;
+ free(removed);
+ }
+ zwp_tablet_v2_destroy(tablet);
+}
+
+static const struct zwp_tablet_v2_listener tablet_listener = {
+ .name = tablet_name, .id = tablet_id, .path = tablet_path,
+ .done = tablet_done, .removed = tablet_removed, .bustype = tablet_bustype,
+};
+
+static void tablet_pad_group(void *data, struct zwp_tablet_pad_v2 *pad, struct zwp_tablet_pad_group_v2 *group) { (void)data; (void)pad; (void)group; }
+static void tablet_pad_path(void *data, struct zwp_tablet_pad_v2 *pad, const char *path) { (void)data; (void)pad; (void)path; }
+static void tablet_pad_buttons(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t buttons) { (void)data; (void)pad; (void)buttons; }
+static void tablet_pad_done(void *data, struct zwp_tablet_pad_v2 *pad) { (void)data; (void)pad; }
+static void tablet_pad_button(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t time, uint32_t button, uint32_t state) { (void)data; (void)pad; (void)time; (void)button; (void)state; }
+static void tablet_pad_enter(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t serial, struct zwp_tablet_v2 *tablet, struct wl_surface *surface) { (void)data; (void)pad; (void)serial; (void)tablet; (void)surface; }
+static void tablet_pad_leave(void *data, struct zwp_tablet_pad_v2 *pad, uint32_t serial, struct wl_surface *surface) { (void)data; (void)pad; (void)serial; (void)surface; }
+
+static void tablet_pad_removed(void *data, struct zwp_tablet_pad_v2 *pad)
+{
+ struct seat *s = data;
+ struct tablet_pad **link = &s->tablet_pads;
+ while (*link && (*link)->proxy != pad)
+ link = &(*link)->next;
+ if (*link) {
+ struct tablet_pad *removed = *link;
+ *link = removed->next;
+ free(removed);
+ }
+ zwp_tablet_pad_v2_destroy(pad);
+}
+
+static const struct zwp_tablet_pad_v2_listener tablet_pad_listener = {
+ .group = tablet_pad_group, .path = tablet_pad_path, .buttons = tablet_pad_buttons,
+ .done = tablet_pad_done, .button = tablet_pad_button, .enter = tablet_pad_enter,
+ .leave = tablet_pad_leave, .removed = tablet_pad_removed,
+};
+
+void seat_destroy_tablet(struct seat *s)
+{
+ while (s->tablet_tools)
+ tablet_tool_removed(s->tablet_tools, s->tablet_tools->proxy);
+ while (s->tablet_pads)
+ tablet_pad_removed(s, s->tablet_pads->proxy);
+ while (s->tablet_devices)
+ tablet_removed(s, s->tablet_devices->proxy);
+ if (s->tablet_seat) {
+ zwp_tablet_seat_v2_destroy(s->tablet_seat);
+ s->tablet_seat = NULL;
+ }
+}
+
+static void tablet_seat_tablet_added(void *data, struct zwp_tablet_seat_v2 *tablet_seat, struct zwp_tablet_v2 *tablet)
+{
+ (void)tablet_seat;
+ struct seat *s = data;
+ struct tablet_device *owned = calloc(1, sizeof(*owned));
+ if (!owned) { zwp_tablet_v2_destroy(tablet); return; }
+ owned->proxy = tablet;
+ owned->next = s->tablet_devices;
+ s->tablet_devices = owned;
+ zwp_tablet_v2_add_listener(tablet, &tablet_listener, s);
+}
+
+static void tablet_seat_tool_added(void *data, struct zwp_tablet_seat_v2 *tablet_seat, struct zwp_tablet_tool_v2 *tool)
+{
+ (void)tablet_seat;
+ struct seat *s = data;
+ struct tablet_tool *owned = calloc(1, sizeof(*owned));
+ if (!owned) { zwp_tablet_tool_v2_destroy(tool); return; }
+ owned->proxy = tool;
+ owned->seat = s;
+ owned->next = s->tablet_tools;
+ s->tablet_tools = owned;
+ zwp_tablet_tool_v2_add_listener(tool, &tablet_tool_listener, owned);
+}
+
+static void tablet_seat_pad_added(void *data, struct zwp_tablet_seat_v2 *tablet_seat, struct zwp_tablet_pad_v2 *pad)
+{
+ (void)tablet_seat;
+ struct seat *s = data;
+ struct tablet_pad *owned = calloc(1, sizeof(*owned));
+ if (!owned) { zwp_tablet_pad_v2_destroy(pad); return; }
+ owned->proxy = pad;
+ owned->next = s->tablet_pads;
+ s->tablet_pads = owned;
+ zwp_tablet_pad_v2_add_listener(pad, &tablet_pad_listener, s);
+}
+
+static const struct zwp_tablet_seat_v2_listener tablet_seat_listener = {
+ .tablet_added = tablet_seat_tablet_added,
+ .tool_added = tablet_seat_tool_added,
+ .pad_added = tablet_seat_pad_added,
+};
+
+static void clipboard_received(const char *text, size_t len, bool uri_list,
+ struct wl_surface *on, void *ud)
+{
+ struct seat *s = ud;
+ struct yasl_display *c = s->conn;
+ struct yasl_surface *w = on ? surface_root(conn_surface_for(c, on)) : NULL;
+ if (!w)
+ w = c->paste_target ? c->paste_target : s->keyboard_focus;
+ if (!len || !w) return;
+ struct yasl_input_event ev = {
+ .surface = w,
+ .kind = uri_list ? YASL_INPUT_DROP_FILES : YASL_INPUT_PASTE,
+ };
+ if (uri_list) {
+ ev.drop_files.files = text;
+ ev.drop_files.len = len;
+ } else {
+ ev.paste.text = text;
+ ev.paste.len = len;
+ }
+ input_queue_push(&w->input, &ev);
+}
+
+static void clipboard_drag(enum yasl_drag_event ev, struct wl_surface *on,
+ double x, double y, void *ud)
+{
+ struct seat *s = ud;
+ conn_drag_target(s->conn, ev, on, x, y);
+}
+
+static void clipboard_drop_hover(enum yasl_drop_event ev, struct wl_surface *on,
+ double x, double y, void *ud)
+{
+ struct seat *s = ud;
+ conn_drop_target(s->conn, ev, on, x, y);
+}
+
+void seat_clipboard_ensure(struct seat *s)
+{
+ struct yasl_display *c = s->conn;
+ if (!c->data_manager || !s->proxy)
+ return;
+ if (!s->clipboard) {
+ s->clipboard = clipboard_create(c->wl, c->data_manager, s->proxy,
+ c->loop, clipboard_received, s);
+ if (!s->clipboard)
+ return;
+ clipboard_set_drag_handler(s->clipboard, clipboard_drag, s);
+ clipboard_set_drop_handler(s->clipboard, clipboard_drop_hover, s);
+ }
+ if (c->primary_selection_mgr)
+ clipboard_set_primary_manager(s->clipboard, c->primary_selection_mgr);
+}
+
+static void seat_drop_pointer(struct seat *s)
+{
+ if (s->cursor_shape_dev) {
+ wp_cursor_shape_device_v1_destroy(s->cursor_shape_dev);
+ s->cursor_shape_dev = NULL;
+ }
+ if (s->pointer) {
+ if (wl_pointer_get_version(s->pointer) >= WL_POINTER_RELEASE_SINCE_VERSION)
+ wl_pointer_release(s->pointer);
+ else
+ wl_pointer_destroy(s->pointer);
+ s->pointer = NULL;
+ }
+ seat_pointer_reset(s);
+ s->pointer_focus = s->ptr_left = NULL;
+ s->ptr_presses.n = 0;
+}
+
+static void seat_drop_keyboard(struct seat *s)
+{
+ keyboard_repeat_stop(s);
+ if (s->keyboard) {
+ if (wl_keyboard_get_version(s->keyboard) >= WL_KEYBOARD_RELEASE_SINCE_VERSION)
+ wl_keyboard_release(s->keyboard);
+ else
+ wl_keyboard_destroy(s->keyboard);
+ s->keyboard = NULL;
+ }
+ s->repeat_rate = 0;
+ struct yasl_surface *w = s->keyboard_focus;
+ s->keyboard_focus = NULL;
+ if (w && !s->focus_leaving)
+ s->focus_leaving = w;
+}
+
+static void seat_drop_touch(struct seat *s)
+{
+ if (s->touch) {
+ touch_cancel(s, s->touch);
+ if (wl_touch_get_version(s->touch) >= WL_TOUCH_RELEASE_SINCE_VERSION)
+ wl_touch_release(s->touch);
+ else
+ wl_touch_destroy(s->touch);
+ s->touch = NULL;
+ }
+}
+
+static void seat_capabilities(void *data, struct wl_seat *proxy, uint32_t caps)
+{
+ struct seat *s = data;
+ struct yasl_display *c = s->conn;
+ if (caps & WL_SEAT_CAPABILITY_KEYBOARD) {
+ if (!s->keyboard) {
+ s->keyboard = wl_seat_get_keyboard(proxy);
+ if (s->keyboard)
+ wl_keyboard_add_listener(s->keyboard, &keyboard_listener, s);
+ if (s->keyboard && wl_keyboard_get_version(s->keyboard) <
+ WL_KEYBOARD_REPEAT_INFO_SINCE_VERSION) {
+ s->repeat_rate = 25;
+ s->repeat_delay = 600;
+ }
+ }
+ } else if (s->keyboard) {
+ seat_drop_keyboard(s);
+ }
+
+ if (caps & WL_SEAT_CAPABILITY_POINTER) {
+ if (!s->pointer) {
+ s->pointer = wl_seat_get_pointer(proxy);
+ if (s->pointer)
+ wl_pointer_add_listener(s->pointer, &pointer_listener, s);
+ if (s->pointer && c->cursor_shape_mgr)
+ s->cursor_shape_dev = wp_cursor_shape_manager_v1_get_pointer(
+ c->cursor_shape_mgr, s->pointer);
+ }
+ } else if (s->pointer) {
+ seat_drop_pointer(s);
+ }
+
+ if (caps & WL_SEAT_CAPABILITY_TOUCH) {
+ if (!s->touch) {
+ s->touch = wl_seat_get_touch(proxy);
+ if (s->touch)
+ wl_touch_add_listener(s->touch, &touch_listener, s);
+ }
+ } else if (s->touch) {
+ seat_drop_touch(s);
+ }
+
+ if (c->tablet_mgr && !s->tablet_seat) {
+ s->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(c->tablet_mgr, proxy);
+ if (s->tablet_seat)
+ zwp_tablet_seat_v2_add_listener(s->tablet_seat, &tablet_seat_listener, s);
+ }
+
+ if (c->text_input_mgr && !s->text_input) {
+ s->text_input = zwp_text_input_manager_v3_get_text_input(c->text_input_mgr, proxy);
+ if (s->text_input)
+ zwp_text_input_v3_add_listener(s->text_input, &text_input_listener, s);
+ }
+
+ seat_clipboard_ensure(s);
+}
+
+static void seat_name(void *data, struct wl_seat *seat, const char *name)
+{ (void)data; (void)seat; (void)name; }
+
+const struct wl_seat_listener seat_listener = {
+ .capabilities = seat_capabilities,
+ .name = seat_name,
+};
+
+void seat_bind(struct yasl_display *c, struct wl_registry *registry,
+ uint32_t name, uint32_t version)
+{
+ struct seat *s = calloc(1, sizeof(*s));
+ if (!s) return;
+ s->conn = c;
+ s->name = name;
+ s->repeat_fd = -1;
+ s->cursor_shape = -1;
+ s->proxy = wl_registry_bind(registry, name, &wl_seat_interface,
+ version < 8 ? version : 8);
+ if (!s->proxy) {
+ free(s);
+ return;
+ }
+ s->next = c->seats;
+ c->seats = s;
+ wl_seat_add_listener(s->proxy, &seat_listener, s);
+ keyboard_repeat_init(s);
+}
+
+void seat_forget_surface(struct seat *s, struct yasl_surface *w)
+{
+ struct yasl_surface **refs[] = {
+ &s->pointer_focus, &s->keyboard_focus,
+ &s->tablet_focus, &s->ime_focus, &s->focus_leaving, &s->ptr_left,
+ &s->press_surface,
+ };
+ for (size_t i = 0; i < sizeof(refs) / sizeof(refs[0]); i++)
+ if (*refs[i] == w)
+ *refs[i] = NULL;
+ press_queue_forget(&s->ptr_presses, w);
+ press_queue_forget(&s->tool_presses, w);
+ for (int i = 0; i < SEAT_TOUCH_POINTS; i++) {
+ if (s->touch_points[i].active && s->touch_points[i].surface == w) {
+ if (s->touch_primary == &s->touch_points[i]) {
+ s->touch_primary = NULL;
+ s->touch_pressed = false;
+ }
+ s->touch_points[i] = (struct touch_point){0};
+ }
+ }
+}
+
+void seat_destroy(struct seat *s)
+{
+ struct yasl_display *c = s->conn;
+ for (struct seat **link = &c->seats; *link; link = &(*link)->next) {
+ if (*link == s) {
+ *link = s->next;
+ break;
+ }
+ }
+ if (c->active_seat == s)
+ c->active_seat = NULL;
+ for (struct yasl_surface *w = c->surfaces; w; w = w->conn_next) {
+ if (w->ime_seat == s) {
+ w->ime_seat = NULL;
+ ime_reset(w);
+ }
+ }
+ if (c->drag.seat == s)
+ conn_drag_end(c);
+ if (s->text_input)
+ zwp_text_input_v3_destroy(s->text_input);
+ seat_destroy_tablet(s);
+ seat_drop_pointer(s);
+ seat_drop_keyboard(s);
+ struct yasl_surface *left = s->focus_leaving;
+ if (left && !conn_keyboard_on(c, left, NULL)) {
+ struct yasl_input_event ev = {
+ .kind = YASL_INPUT_FOCUS,
+ .surface = left,
+ .focus = { .focused = false },
+ };
+ input_queue_push(&left->input, &ev);
+ }
+ seat_drop_touch(s);
+ clipboard_destroy(s->clipboard);
+ if (s->repeat_watch > 0) yasl_loop_del_fd(c->loop, s->repeat_watch);
+ if (s->repeat_fd >= 0) close(s->repeat_fd);
+ if (s->cursor_viewport) wp_viewport_destroy(s->cursor_viewport);
+ if (s->cursor_surface) wl_surface_destroy(s->cursor_surface);
+ if (s->xkb_state) xkb_state_unref(s->xkb_state);
+ if (s->xkb_keymap) xkb_keymap_unref(s->xkb_keymap);
+ if (s->proxy) {
+ if (wl_seat_get_version(s->proxy) >= WL_SEAT_RELEASE_SINCE_VERSION)
+ wl_seat_release(s->proxy);
+ else
+ wl_seat_destroy(s->proxy);
+ }
+ free(s);
+}
+
+bool seat_remove(struct yasl_display *c, uint32_t name)
+{
+ for (struct seat *s = c->seats; s; s = s->next) {
+ if (s->name == name) {
+ seat_destroy(s);
+ return true;
+ }
+ }
+ return false;
+}
+
+int display_input_init(struct yasl_display *c)
+{
+ for (struct seat *s = c->seats; s; s = s->next) {
+ keyboard_repeat_init(s);
+ seat_clipboard_ensure(s);
+ }
+ return 0;
+}
+
+void surface_init_cursor(struct yasl_display *c)
+{
+ if (c->cursor_shape_mgr)
+ return;
+ const char *env_size = getenv("XCURSOR_SIZE");
+ int size = env_size ? atoi(env_size) : 0;
+ c->cursor_theme_name = getenv("XCURSOR_THEME");
+ c->cursor_size = size > 0 && size <= 512 ? size : 24;
+ cursor_theme_for(c, 1);
+}