From a6b3d52f5d2b6d4fc0322271966b54931abf7034 Mon Sep 17 00:00:00 2001 From: Lasse Heitgres Date: Tue, 9 Jun 2026 13:15:53 +0200 Subject: [PATCH 1/2] 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; }; From 2ccfa6d73682b3556575e487dcb8d155b0604940 Mon Sep 17 00:00:00 2001 From: Lasse Heitgres Date: Wed, 10 Jun 2026 14:08:23 +0200 Subject: [PATCH 2/2] backend/wayland: implement multi-output touch --- backend/wayland/backend.c | 4 - backend/wayland/output.c | 3 + backend/wayland/seat.c | 224 +++++++++++++++++++++++++------------- include/backend/wayland.h | 18 ++- 4 files changed, 168 insertions(+), 81 deletions(-) diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 0d294b543..308aefd9d 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -457,10 +457,6 @@ static bool backend_start(struct wlr_backend *backend) { init_seat_keyboard(seat); } - if (seat->wl_touch) { - init_seat_touch(seat); - } - if (wl->tablet_manager) { init_seat_tablet(seat); } diff --git a/backend/wayland/output.c b/backend/wayland/output.c index c3442f411..212c42e66 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -1151,6 +1151,9 @@ static void output_start(struct wlr_wl_output *output) { if (seat->wl_pointer) { create_pointer(seat, output); } + if (seat->wl_touch) { + create_touch(seat, output); + } } } diff --git a/backend/wayland/seat.c b/backend/wayland/seat.c index 1da1065e7..f82770630 100644 --- a/backend/wayland/seat.c +++ b/backend/wayland/seat.c @@ -110,76 +110,73 @@ void init_seat_keyboard(struct wlr_wl_seat *seat) { &seat->wlr_keyboard.base); } -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; -} - -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]; +static struct wlr_wl_touch *touch_for_output(struct wlr_wl_seat *seat, + struct wlr_wl_output *output) { + struct wlr_wl_touch *touch; + wl_list_for_each(touch, &seat->touches, link) { + if (touch->output == output) { + return touch; } } return NULL; } -static void touch_set_output(struct wlr_wl_seat *seat, - struct wlr_wl_output *output) { - if (output == NULL) { - return; +static struct wlr_wl_touch *touch_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->touches[i]; + } } - 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); + return NULL; } -static void touch_coordinates_to_absolute(struct wlr_wl_output *output, +static void touch_coordinates_to_absolute(struct wlr_wl_touch *touch, 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) { + struct wlr_output *output = &touch->output->wlr_output; + if (output->width <= 0 || 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; + *sx = wl_fixed_to_double(x) / output->width; + *sy = wl_fixed_to_double(y) / output->height; } static void touch_handle_down(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { 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 (surface == NULL) { + return; + } + + struct wlr_wl_output *output = + get_wl_output_from_surface(seat->backend, surface); if (output == NULL) { - output = first_wl_output(seat); + return; + } + + struct wlr_wl_touch *touch = touch_for_output(seat, output); + if (touch == NULL) { + return; } 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->touches[points->len] = touch; points->ids[points->len++] = id; - touch_set_output(seat, output); + touch->needs_frame = true; struct wlr_touch_down_event event = { - .touch = touch, + .touch = &touch->wlr_touch, .time_msec = time, .touch_id = id, }; - touch_coordinates_to_absolute(output, x, y, &event.x, &event.y); - wl_signal_emit_mutable(&touch->events.down, &event); + touch_coordinates_to_absolute(touch, x, y, &event.x, &event.y); + wl_signal_emit_mutable(&touch->wlr_touch.events.down, &event); } static bool remove_touch_point(struct wlr_wl_touch_points *points, int32_t id) { @@ -188,8 +185,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])); + memmove(&points->touches[i], &points->touches[i + 1], + remaining * sizeof(points->touches[0])); points->len--; return true; } @@ -200,55 +197,70 @@ static bool remove_touch_point(struct wlr_wl_touch_points *points, int32_t id) { static void touch_handle_up(void *data, struct wl_touch *wl_touch, uint32_t serial, uint32_t time, int32_t id) { struct wlr_wl_seat *seat = data; - struct wlr_touch *touch = &seat->wlr_touch; + struct wlr_wl_touch *touch = touch_for_id(seat, id); remove_touch_point(&seat->touch_points, id); + if (touch == NULL) { + return; + } + + touch->needs_frame = true; struct wlr_touch_up_event event = { - .touch = touch, + .touch = &touch->wlr_touch, .time_msec = time, .touch_id = id, }; - wl_signal_emit_mutable(&touch->events.up, &event); + wl_signal_emit_mutable(&touch->wlr_touch.events.up, &event); } static void touch_handle_motion(void *data, struct wl_touch *wl_touch, uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { 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_wl_touch *touch = touch_for_id(seat, id); + if (touch == NULL) { + return; + } + + touch->needs_frame = true; struct wlr_touch_motion_event event = { - .touch = touch, + .touch = &touch->wlr_touch, .time_msec = time, .touch_id = id, }; - - touch_coordinates_to_absolute(output, x, y, &event.x, &event.y); - wl_signal_emit_mutable(&touch->events.motion, &event); + touch_coordinates_to_absolute(touch, x, y, &event.x, &event.y); + wl_signal_emit_mutable(&touch->wlr_touch.events.motion, &event); } static void touch_handle_frame(void *data, struct wl_touch *wl_touch) { struct wlr_wl_seat *seat = data; - wl_signal_emit_mutable(&seat->wlr_touch.events.frame, NULL); + + struct wlr_wl_touch *touch; + wl_list_for_each(touch, &seat->touches, link) { + if (touch->needs_frame) { + touch->needs_frame = false; + wl_signal_emit_mutable(&touch->wlr_touch.events.frame, NULL); + } + } } static void touch_handle_cancel(void *data, struct wl_touch *wl_touch) { struct wlr_wl_seat *seat = data; - struct wlr_touch *touch = &seat->wlr_touch; // wayland's cancel event applies to all active touch points - for (size_t i = 0; i < seat->touch_points.len; i++) { + struct wlr_wl_touch_points *points = &seat->touch_points; + for (size_t i = 0; i < points->len; i++) { + struct wlr_wl_touch *touch = points->touches[i]; struct wlr_touch_cancel_event event = { - .touch = touch, + .touch = &touch->wlr_touch, .time_msec = 0, - .touch_id = seat->touch_points.ids[i], + .touch_id = points->ids[i], }; - wl_signal_emit_mutable(&touch->events.cancel, &event); + wl_signal_emit_mutable(&touch->wlr_touch.events.cancel, &event); } - seat->touch_points.len = 0; + points->len = 0; } static void touch_handle_shape(void *data, struct wl_touch *wl_touch, @@ -275,23 +287,91 @@ static const struct wlr_touch_impl touch_impl = { .name = "wl-touch", }; -void init_seat_touch(struct wlr_wl_seat *seat) { +static void destroy_touch(struct wlr_wl_touch *touch) { + struct wlr_wl_seat *seat = touch->seat; + + // Forget any active touch points held by this device + struct wlr_wl_touch_points *points = &seat->touch_points; + for (size_t i = points->len; i-- > 0;) { + if (points->touches[i] == touch) { + remove_touch_point(points, points->ids[i]); + } + } + + wlr_touch_finish(&touch->wlr_touch); + wl_list_remove(&touch->output_destroy.link); + wl_list_remove(&touch->link); + free(touch); +} + +static void touch_output_destroy(struct wl_listener *listener, void *data) { + struct wlr_wl_touch *touch = + wl_container_of(listener, touch, output_destroy); + destroy_touch(touch); +} + +void create_touch(struct wlr_wl_seat *seat, struct wlr_wl_output *output) { assert(seat->wl_touch); + if (touch_for_output(seat, output)) { + wlr_log(WLR_DEBUG, + "touch for output '%s' from seat '%s' already exists", + output->wlr_output.name, seat->name); + return; + } + + wlr_log(WLR_DEBUG, "creating touch for output '%s' from seat '%s'", + output->wlr_output.name, seat->name); + + struct wlr_wl_touch *touch = calloc(1, sizeof(*touch)); + if (touch == NULL) { + wlr_log(WLR_ERROR, "failed to allocate wlr_wl_touch"); + return; + } + char name[128] = {0}; snprintf(name, sizeof(name), "wayland-touch-%s", seat->name); + wlr_touch_init(&touch->wlr_touch, &touch_impl, name); - wlr_touch_init(&seat->wlr_touch, &touch_impl, name); + touch->wlr_touch.output_name = strdup(output->wlr_output.name); - 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); + touch->seat = seat; + touch->output = output; + + wl_signal_add(&output->wlr_output.events.destroy, &touch->output_destroy); + touch->output_destroy.notify = touch_output_destroy; + + wl_signal_emit_mutable(&seat->backend->backend.events.new_input, + &touch->wlr_touch.base); + + wl_list_insert(&seat->touches, &touch->link); +} + +void init_seat_touch(struct wlr_wl_seat *seat) { + assert(seat->wl_touch); + + wl_list_init(&seat->touches); + + struct wlr_wl_output *output; + wl_list_for_each(output, &seat->backend->outputs, link) { + create_touch(seat, output); } wl_touch_add_listener(seat->wl_touch, &touch_listener, seat); - wl_signal_emit_mutable(&seat->backend->backend.events.new_input, - &seat->wlr_touch.base); +} + +void finish_seat_touch(struct wlr_wl_seat *seat) { + assert(seat->wl_touch); + + wl_touch_release(seat->wl_touch); + + struct wlr_wl_touch *touch, *tmp; + wl_list_for_each_safe(touch, tmp, &seat->touches, link) { + destroy_touch(touch); + } + + seat->touch_points.len = 0; + seat->wl_touch = NULL; } static const struct wl_seat_listener seat_listener; @@ -313,8 +393,7 @@ bool create_wl_seat(struct wl_seat *wl_seat, struct wlr_wl_backend *wl, void destroy_wl_seat(struct wlr_wl_seat *seat) { if (seat->wl_touch) { - wl_touch_release(seat->wl_touch); - wlr_touch_finish(&seat->wlr_touch); + finish_seat_touch(seat); } if (seat->wl_pointer) { finish_seat_pointer(seat); @@ -394,16 +473,11 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, wlr_log(WLR_DEBUG, "seat '%s' offering touch", seat->name); seat->wl_touch = wl_seat_get_touch(wl_seat); - if (backend->started) { - init_seat_touch(seat); - } + init_seat_touch(seat); } if (!(caps & WL_SEAT_CAPABILITY_TOUCH) && seat->wl_touch != NULL) { wlr_log(WLR_DEBUG, "seat '%s' dropping touch", seat->name); - - wl_touch_release(seat->wl_touch); - wlr_touch_finish(&seat->wlr_touch); - seat->wl_touch = NULL; + finish_seat_touch(seat); } } diff --git a/include/backend/wayland.h b/include/backend/wayland.h index 1788bcc99..2fb892609 100644 --- a/include/backend/wayland.h +++ b/include/backend/wayland.h @@ -149,9 +149,21 @@ struct wlr_wl_pointer { struct wl_list link; }; +struct wlr_wl_touch { + struct wlr_touch wlr_touch; + + struct wlr_wl_seat *seat; + struct wlr_wl_output *output; + bool needs_frame; + + struct wl_listener output_destroy; + + struct wl_list link; +}; + struct wlr_wl_touch_points { int32_t ids[64]; - struct wlr_wl_output *outputs[64]; + struct wlr_wl_touch *touches[64]; size_t len; }; @@ -175,7 +187,7 @@ struct wlr_wl_seat { struct zwp_relative_pointer_v1 *relative_pointer; struct wl_touch *wl_touch; - struct wlr_touch wlr_touch; + struct wl_list touches; struct wlr_wl_touch_points touch_points; struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2; @@ -201,6 +213,8 @@ void finish_seat_pointer(struct wlr_wl_seat *seat); void create_pointer(struct wlr_wl_seat *seat, struct wlr_wl_output *output); void init_seat_touch(struct wlr_wl_seat *seat); +void finish_seat_touch(struct wlr_wl_seat *seat); +void create_touch(struct wlr_wl_seat *seat, struct wlr_wl_output *output); void init_seat_tablet(struct wlr_wl_seat *seat); void finish_seat_tablet(struct wlr_wl_seat *seat);