From 8e44489bf7c5f0e4d2c9c2ba5468dba08a0ca4d6 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 27 Mar 2026 10:40:32 +0100 Subject: [PATCH 1/2] compositor: add wlr_surface_queue_lazy_leave() And also wlr_surface_process_lazy_leaves() This is motivated by fixing inconsistent enter/leave events sent by wlr_scene when there are multiple scenes active with distinct outputs. --- include/wlr/types/wlr_compositor.h | 29 ++++++++++++++++++++++ types/wlr_compositor.c | 40 ++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h index 9331e8af6..fca325f53 100644 --- a/include/wlr/types/wlr_compositor.h +++ b/include/wlr/types/wlr_compositor.h @@ -127,6 +127,8 @@ struct wlr_surface_output { struct { struct wl_listener bind; struct wl_listener destroy; + + bool lazy_leave; } WLR_PRIVATE; }; @@ -399,6 +401,33 @@ void wlr_surface_send_enter(struct wlr_surface *surface, void wlr_surface_send_leave(struct wlr_surface *surface, struct wlr_output *output); +/** + * Queue a lazy wl_surface.leave event to be sent in a future call to + * wlr_surface_process_lazy_leaves(). + * + * If wlr_surface_send_enter() is called before wlr_surface_process_lazy_leaves() + * actually sends a leave event, the lazy leave mark is cleared. + */ +void wlr_surface_queue_lazy_leave(struct wlr_surface *surface, struct wlr_output *output); + +/** + * If at least one output would remain entered after all lazy leave events + * are sent, send all lazy leave events. Otherwise do nothing. + * + * This behavior helps the compositor implement the following recommended policy: + * + * 1. When a surface transitions from being visible on >0 outputs to being visible on 0 outputs + * don't send any leave events. + * + * 2. When a surface transitions from being visible on 0 outputs to being visible on >0 outputs + * send leave events for all entered outputs on which the surface is no longer visible as + * well as enter events for any outputs not already entered. + * + * This policy avoids sending redundant enter/leave events when a surface is hidden and then shown + * again without any change to the set of intersected outputs. + */ +void wlr_surface_process_lazy_leaves(struct wlr_surface *surface); + /** * Complete the queued frame callbacks for this surface. * diff --git a/types/wlr_compositor.c b/types/wlr_compositor.c index 6b31ab857..6b7e182b7 100644 --- a/types/wlr_compositor.c +++ b/types/wlr_compositor.c @@ -1084,6 +1084,7 @@ void wlr_surface_send_enter(struct wlr_surface *surface, wl_list_for_each(surface_output, &surface->current_outputs, link) { if (surface_output->output == output) { + surface_output->lazy_leave = false; return; } } @@ -1129,6 +1130,45 @@ void wlr_surface_send_leave(struct wlr_surface *surface, } } +void wlr_surface_queue_lazy_leave(struct wlr_surface *surface, struct wlr_output *output) { + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (surface_output->output == output) { + surface_output->lazy_leave = true; + } + } +} + +void wlr_surface_process_lazy_leaves(struct wlr_surface *surface) { + // Only send leave events if at least one output would remain entered. + bool send_leave_events = false; + struct wlr_surface_output *surface_output; + wl_list_for_each(surface_output, &surface->current_outputs, link) { + if (!surface_output->lazy_leave) { + send_leave_events = true; + } + } + + if (!send_leave_events) { + return; + } + + struct wl_client *client = wl_resource_get_client(surface->resource); + struct wlr_surface_output *tmp; + wl_list_for_each_safe(surface_output, tmp, &surface->current_outputs, link) { + if (surface_output->lazy_leave) { + struct wl_resource *resource; + wl_resource_for_each(resource, &surface_output->output->resources) { + if (client == wl_resource_get_client(resource)) { + wl_surface_send_leave(surface->resource, resource); + } + } + surface_output_destroy(surface_output); + break; + } + } +} + void wlr_surface_send_frame_done(struct wlr_surface *surface, const struct timespec *when) { struct wl_resource *resource, *tmp; From fc6410448ed04068a914d62a9df05c7bde322877 Mon Sep 17 00:00:00 2001 From: Isaac Freund Date: Fri, 27 Mar 2026 10:42:29 +0100 Subject: [PATCH 2/2] scene/surface: fix enter/leave events with multiple scenes Currently if there are multiple scenes present with distinct outputs the logic intended to avoid redundant enter/leave events does not work as intended. A surface not being visible on any output in one scene does not mean it is not visible on any output in any scene. Implementing the desired policy to avoid redundant enter/leave events requires sharing a bit of state between all scenes, which is now handled by the new wlr_surface_send_leave_lazy() function. --- types/scene/surface.c | 38 +++++++++----------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/types/scene/surface.c b/types/scene/surface.c index e6ea1333a..29583c6ef 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -97,41 +97,21 @@ static void handle_scene_buffer_outputs_update( struct wlr_scene_outputs_update_event *event = data; struct wlr_scene *scene = scene_node_get_root(&surface->buffer->node); + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, &scene->outputs, link) { + wlr_surface_queue_lazy_leave(surface->surface, scene_output->output); + } + for (size_t i = 0; i < event->size; i++) { + wlr_surface_send_enter(surface->surface, event->active[i]->output); + } + wlr_surface_process_lazy_leaves(surface->surface); + // If the surface is no longer visible on any output, keep the last sent // preferred configuration to avoid unnecessary redraws if (event->size == 0) { return; } - // To avoid sending redundant leave/enter events when a surface is hidden and then shown - // without moving to a different output the following policy is implemented: - // - // 1. When a surface transitions from being visible on >0 outputs to being visible on 0 outputs - // don't send any leave events. - // - // 2. When a surface transitions from being visible on 0 outputs to being visible on >0 outputs - // send leave events for all entered outputs on which the surface is no longer visible as - // well as enter events for any outputs not already entered. - struct wlr_surface_output *entered_output, *tmp; - wl_list_for_each_safe(entered_output, tmp, &surface->surface->current_outputs, link) { - bool active = false; - for (size_t i = 0; i < event->size; i++) { - if (entered_output->output == event->active[i]->output) { - active = true; - break; - } - } - if (!active) { - wlr_surface_send_leave(surface->surface, entered_output->output); - } - } - - for (size_t i = 0; i < event->size; i++) { - // This function internally checks if an enter event was already sent for the output - // to avoid sending redundant events. - wlr_surface_send_enter(surface->surface, event->active[i]->output); - } - double scale = get_surface_preferred_buffer_scale(surface->surface); wlr_fractional_scale_v1_notify_scale(surface->surface, scale); wlr_surface_set_preferred_buffer_scale(surface->surface, ceil(scale));