From 2ccfa6d73682b3556575e487dcb8d155b0604940 Mon Sep 17 00:00:00 2001 From: Lasse Heitgres Date: Wed, 10 Jun 2026 14:08:23 +0200 Subject: [PATCH] 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);