From d9649d630a4adc752ca534c25bb2a8c068fd316d Mon Sep 17 00:00:00 2001 From: Consolatis <35009135+Consolatis@users.noreply.github.com> Date: Sat, 4 Apr 2026 17:15:33 +0200 Subject: [PATCH] toplevel-capture: work around missing frame events due to wlroots bug In wlroots 0.20.0 there is an issue where a capture scene removes outputs from a wlr_surface which belong to another scene. This in turn causes `wlr_scene_output_send_frame_done()` to fail for our main scene after a toplevel has been captured once, for example via grim -T. To work around the issue we look for views with a capture session, iterate over all its wlr_scene_buffers and if they have the primary output set (e.g. they are not completely covered / hidden) we send the frame event manually. As wlroots turns multiple frame events without a new callback registered by the client into no-ops this also doesn't result in duplicated frame events seen by the client. Note that the other way around also causes an issue which this workaround does not fix. When capturing a toplevel and then changing its visibility state on screen and then covering it up completely / hiding it the capture will stall instead. --- src/output.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/output.c b/src/output.c index aea35062..4042cc1c 100644 --- a/src/output.c +++ b/src/output.c @@ -51,6 +51,36 @@ #include #endif +/* + * Partial workaround for toplevel capture on wlroots 0.20.0 + * Note that even with this workaround the capture itself might still stall. + * + * See https://gitlab.freedesktop.org/wlroots/wlroots/-/merge_requests/5315 + * + * TODO: Remove once we start tracking wlroots 0.21.x + * or labwc depends on wlroots >= 0.20.1 + */ + +#include + +static void +workaround_frame_done_iter(struct wlr_scene_buffer *buffer, int sx, int sy, void *data) +{ + if (!buffer->primary_output) { + /* Catches hidden views, e.g. completely covered or disabled */ + return; + } + + struct wlr_scene_surface *scene_surface = wlr_scene_surface_try_from_buffer(buffer); + if (!scene_surface) { + return; + } + + wlr_surface_send_frame_done(scene_surface->surface, (struct timespec *)data); +} + +/* Workaround for toplevel capture end */ + bool output_get_tearing_allowance(struct output *output) { @@ -121,6 +151,29 @@ handle_output_frame(struct wl_listener *listener, void *data) struct timespec now = { 0 }; clock_gettime(CLOCK_MONOTONIC, &now); wlr_scene_output_send_frame_done(output->scene_output, &now); + + /* + * Workaround for toplevel capture on wlroots 0.20.0 + * + * TODO: Remove once we start tracking wlroots 0.21.x + * or labwc depends on wlroots >= 0.20.1 + */ + if (LAB_WLR_VERSION_LOWER(0, 20, 1)) { + struct view *view; + wl_list_for_each(view, &server.views, link) { + if (view->capture.source) { + if (!view_on_output(view, output)) { + continue; + } + /* + * view might still be covered / disabled, + * but the iterator takes care of that. + */ + wlr_scene_node_for_each_buffer(&view->content_tree->node, + workaround_frame_done_iter, &now); + } + } + } } static void