From 7375fbda1693b1e027f41900ca9cd627de3c99c5 Mon Sep 17 00:00:00 2001 From: Ronan Pigott Date: Thu, 26 Mar 2026 20:04:25 -0700 Subject: [PATCH] scene: only send leave events to outputs with matching scene root In handling scene buffer output updates, wlroots would send a leave event to all entered outputs, even those that the scene root for the scene output update event did not own. Leaving the output list inaccurate. Sending leave events only for the given scene introduces a problem, though: existing logic to de-duplicate leave events stops us from sending a leave event when we leave all the outputs in a scene, and when the surface then becomes visible in another scene, the frame pacing output cannot be selected accurately. This breaks screen capture for off-screen windows in sway. So, let us also mark outputs that would have been left but were spared by the deduplication logic as "suspended" indicating they are ineligible as frame pacing outputs. Fixes: https://github.com/swaywm/sway/issues/9094 --- include/wlr/types/wlr_compositor.h | 2 ++ types/scene/surface.c | 29 ++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/include/wlr/types/wlr_compositor.h b/include/wlr/types/wlr_compositor.h index 9331e8af6..aff42aea6 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 suspended; } WLR_PRIVATE; }; diff --git a/types/scene/surface.c b/types/scene/surface.c index e6ea1333a..64928ca72 100644 --- a/types/scene/surface.c +++ b/types/scene/surface.c @@ -30,8 +30,8 @@ static struct wlr_output *get_surface_frame_pacing_output(struct wlr_surface *su struct wlr_output *frame_pacing_output = NULL; struct wlr_surface_output *surface_output; wl_list_for_each(surface_output, &surface->current_outputs, link) { - if (frame_pacing_output == NULL || - surface_output->output->refresh > frame_pacing_output->refresh) { + if (!surface_output->suspended && (frame_pacing_output == NULL || + surface_output->output->refresh > frame_pacing_output->refresh)) { frame_pacing_output = surface_output->output; } } @@ -97,11 +97,9 @@ 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); - // 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; - } + // If the surface is no longer visible on any output in the scene, keep the + // last sent preferred configuration to avoid unnecessary redraws + bool suspend = event->size == 0; // 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: @@ -121,11 +119,24 @@ static void handle_scene_buffer_outputs_update( break; } } - if (!active) { - wlr_surface_send_leave(surface->surface, entered_output->output); + + struct wlr_scene_output *scene_output; + wl_list_for_each(scene_output, &scene->outputs, link) { + if (scene_output->output == entered_output->output) { + entered_output->suspended = suspend; + if (!suspend && !active) { + wlr_surface_send_leave(surface->surface, entered_output->output); + } + break; + } } } + // No reason to update the preferred configuration if we aren't sending leave/enter events. + if (suspend) { + return; + } + 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.