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.
This commit is contained in:
Consolatis 2026-04-04 17:15:33 +02:00
parent facf3856cb
commit d9649d630a

View file

@ -51,6 +51,36 @@
#include <wlr/backend/session.h>
#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 <wlr/types/wlr_compositor.h>
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