From a6b3d52f5d2b6d4fc0322271966b54931abf7034 Mon Sep 17 00:00:00 2001 From: Lasse Heitgres Date: Tue, 9 Jun 2026 13:15:53 +0200 Subject: [PATCH] backend/wayland: implement multi-output touch The wayland backend mapped every touch event to the first output and carried a TODO asking for multi-output support. Track the output each touch point belongs to: on touch down, resolve it from the event's wl_surface (falling back to the first output), store it per touch id, and convert coordinates against that output for down and motion events. This makes touch work correctly when the backend drives multiple outputs. Closes #4102 --- backend/wayland/seat.c | 80 +++++++++++++++++++++++++++++---------- include/backend/wayland.h | 1 + 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/backend/wayland/seat.c b/backend/wayland/seat.c index f178d9baf..1da1065e7 100644 --- a/backend/wayland/seat.c +++ b/backend/wayland/seat.c @@ -110,23 +110,48 @@ void init_seat_keyboard(struct wlr_wl_seat *seat) { &seat->wlr_keyboard.base); } -static void touch_coordinates_to_absolute(struct wlr_wl_seat *seat, - wl_fixed_t x, wl_fixed_t y, double *sx, double *sy) { - /** - * TODO: multi-output touch support - * Although the wayland backend supports multi-output pointers, the support - * for multi-output touch has been left on the side for simplicity reasons. - * If this is a feature you want/need, please open an issue on the wlroots - * tracker here https://gitlab.freedesktop.org/wlroots/wlroots/-/issues - */ - struct wlr_wl_output *output, *tmp; - wl_list_for_each_safe(output, tmp, &seat->backend->outputs, link) { - *sx = wl_fixed_to_double(x) / output->wlr_output.width; - *sy = wl_fixed_to_double(y) / output->wlr_output.height; - return; // Choose the first output in the list +static struct wlr_wl_output *first_wl_output(struct wlr_wl_seat *seat) { + struct wlr_wl_output *output; + wl_list_for_each(output, &seat->backend->outputs, link) { + return output; } + return NULL; +} - *sx = *sy = 0; +static struct wlr_wl_output *touch_output_for_id(struct wlr_wl_seat *seat, + int32_t id) { + struct wlr_wl_touch_points *points = &seat->touch_points; + for (size_t i = 0; i < points->len; i++) { + if (points->ids[i] == id) { + return points->outputs[i]; + } + } + return NULL; +} + +static void touch_set_output(struct wlr_wl_seat *seat, + struct wlr_wl_output *output) { + if (output == NULL) { + return; + } + const char *name = output->wlr_output.name; + if (seat->wlr_touch.output_name != NULL && + strcmp(seat->wlr_touch.output_name, name) == 0) { + return; + } + free(seat->wlr_touch.output_name); + seat->wlr_touch.output_name = strdup(name); +} + +static void touch_coordinates_to_absolute(struct wlr_wl_output *output, + wl_fixed_t x, wl_fixed_t y, double *sx, double *sy) { + if (output == NULL || output->wlr_output.width <= 0 || + output->wlr_output.height <= 0) { + *sx = *sy = 0; + return; + } + *sx = wl_fixed_to_double(x) / output->wlr_output.width; + *sy = wl_fixed_to_double(y) / output->wlr_output.height; } static void touch_handle_down(void *data, struct wl_touch *wl_touch, @@ -135,16 +160,25 @@ static void touch_handle_down(void *data, struct wl_touch *wl_touch, struct wlr_wl_seat *seat = data; struct wlr_touch *touch = &seat->wlr_touch; + struct wlr_wl_output *output = surface != NULL ? + get_wl_output_from_surface(seat->backend, surface) : NULL; + if (output == NULL) { + output = first_wl_output(seat); + } + struct wlr_wl_touch_points *points = &seat->touch_points; assert(points->len != sizeof(points->ids) / sizeof(points->ids[0])); + points->outputs[points->len] = output; points->ids[points->len++] = id; + touch_set_output(seat, output); + struct wlr_touch_down_event event = { .touch = touch, .time_msec = time, .touch_id = id, }; - touch_coordinates_to_absolute(seat, x, y, &event.x, &event.y); + touch_coordinates_to_absolute(output, x, y, &event.x, &event.y); wl_signal_emit_mutable(&touch->events.down, &event); } @@ -154,6 +188,8 @@ static bool remove_touch_point(struct wlr_wl_touch_points *points, int32_t id) { if (points->ids[i] == id) { size_t remaining = points->len - i - 1; memmove(&points->ids[i], &points->ids[i + 1], remaining * sizeof(id)); + memmove(&points->outputs[i], &points->outputs[i + 1], + remaining * sizeof(points->outputs[0])); points->len--; return true; } @@ -181,13 +217,16 @@ static void touch_handle_motion(void *data, struct wl_touch *wl_touch, struct wlr_wl_seat *seat = data; struct wlr_touch *touch = &seat->wlr_touch; + struct wlr_wl_output *output = touch_output_for_id(seat, id); + touch_set_output(seat, output); + struct wlr_touch_motion_event event = { .touch = touch, .time_msec = time, .touch_id = id, }; - touch_coordinates_to_absolute(seat, x, y, &event.x, &event.y); + touch_coordinates_to_absolute(output, x, y, &event.x, &event.y); wl_signal_emit_mutable(&touch->events.motion, &event); } @@ -244,11 +283,10 @@ void init_seat_touch(struct wlr_wl_seat *seat) { wlr_touch_init(&seat->wlr_touch, &touch_impl, name); - struct wlr_wl_output *output; - wl_list_for_each(output, &seat->backend->outputs, link) { - /* Multi-output touch not supported */ + struct wlr_wl_output *output = first_wl_output(seat); + if (output != NULL) { + /* Default until the first touch point lands on a known output */ seat->wlr_touch.output_name = strdup(output->wlr_output.name); - break; } wl_touch_add_listener(seat->wl_touch, &touch_listener, seat); diff --git a/include/backend/wayland.h b/include/backend/wayland.h index e24eb9fdf..1788bcc99 100644 --- a/include/backend/wayland.h +++ b/include/backend/wayland.h @@ -151,6 +151,7 @@ struct wlr_wl_pointer { struct wlr_wl_touch_points { int32_t ids[64]; + struct wlr_wl_output *outputs[64]; size_t len; };