feat: keep hidden overlay-preview thumbnails live while held

Hidden tabs live in disabled scene trees, so
wlr_scene_output_send_frame_done() never reaches them and
frame-callback-throttled clients stop committing: only the shown
window's thumbnail tracked live while the rest sat at their last
frame.

Fan frame-done out to every hidden tab window once per output frame
while the overlay is held (overlay_send_hidden_frames after the scene
frame-done in both render_standalone and render_and_present). The
walk covers main plus subsurface trees for both xdg and X11 windows
via window_surface(), and skips the shown window the scene path
already handles.

Gated on --overlay-previews plus overlay_visible, so the steady state
with the menu hidden costs nothing and background clients are not
woken. The existing thumb_capture snapshot and draw-time tree
composite paths are unchanged; they simply receive the commits they
were missing.

Tests: new test_standalone_headless_overlay_previews_hidden_live uses
a hidden --pulse tab and asserts the red thumbnail count swings while
Alt is held. README overlay-previews paragraph updated from freeze to
live-while-held.

Verified: ./build.sh clean; overlay-previews suite 6 passed; full
suite 179 passed with -n4.
This commit is contained in:
Andrew Hamilton 2026-10-02 10:21:22 +10:00
parent b04f6a2826
commit 80e4e288ad
3 changed files with 71 additions and 4 deletions

View file

@ -6855,3 +6855,33 @@ def test_standalone_headless_overlay_previews_live():
finally:
s.mods(0)
_assert_alive(s)
def test_standalone_headless_overlay_previews_hidden_live():
"""Hidden tabs keep live thumbnails while the overlay is held.
The hidden tab pulses red/black on frame callbacks, so it only
animates while the compositor fans frame-done out to hidden
windows. While Alt is held the red thumbnail pixel count must
swing between phases; without the fan-out it stays frozen."""
with _run_standalone(
{}, "--overlay-previews",
"%s --pulse 400 '#ff0000'" % WL,
"%s '#0000ff'" % WL, wait="any_frame") as s:
s.wait_surface_mapped(0)
s.wait_surface_mapped(1)
s.mods(ALT)
try:
reds = []
deadline = time.monotonic() + 12.0
while time.monotonic() < deadline:
red, _ = _dominant_counts(s, s.dump_host())
reds.append(red)
time.sleep(0.3)
if reds and max(reds) - min(reds) > 1500:
break
assert reds and max(reds) - min(reds) > 1500, \
f"hidden pulse thumbnail frozen while held: range={reds}"
finally:
s.mods(0)
_assert_alive(s)