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.
New --cursor-size N (8..128px, both --cursor-size N and --cursor-size=N forms) sizing the standalone wlr_cursor. Precedence: CLI flag over $XCURSOR_SIZE over the 24px default. Out-of-range, missing or non-numeric values are startup errors (rc 2) naming the flag, matching the existing --start-cmd-index/--tab-groups handling. Nested mode is unaffected (the host draws the pointer there); the flag is silently ignored. Help text, README usage + cursor-shapes section, and a test_cursor_size_flag_errors regression test included. Verified: debug + release builds warning-free; cursor_size + tab-groups flag-error tests pass; --cursor-size 32 boots headless, --cursor-size 999 exits rc 2.
Release builds (gcc 16, -O2) warned in three spots; debug builds only showed the first:
- thumb_extent_iter: copy tex->width/height (uint32_t) into int locals
after the zero guard so all extent math is int<->int.
- dump_path call sites: grow cpath/path buffers 512 -> 1024. Worst
case is ~535 bytes (dump_dir[512] + kind + seq), so gcc can prove
no truncation at every inlined site.
- overlay_find_icon_file: skip overlong XDG roots via snprintf return
checks, fail fast on icon names >1024 chars (can never match a real
file component), and use %.1024s plus truncation guards in both
probe loops (worst case ~1.6k < 4096).
No behaviour change on sane inputs: truncated probes previously just
failed access()/fopen(); now they are skipped explicitly.
Verified: full rebuilds of build/ and build-release/ compile with zero
warnings; toplevel_icon_preferred, sigusr1_dumps and
overlay_no_needless_ellipsis pass.
Remove dead code found by read-only audit of kick.c:
- struct kick_window.transient: never read or written, only the
declaration and generic comments mentioned it.
- struct kick_server.idle_inhibit_destroy: never wired; per-inhibitor
teardown uses handle_idle_inhibitor_destroy via wl_container_of.
- server.panel_pid: write-only (set in launch_panel, never read);
keep the [panel] log using the local pid instead.
- static bool frame_pending: write-only global shadowing
server.output->frame_pending, which is what the main loops actually
poll alongside needs_frame. Drop the global and its four writes;
keep handle_output_frame as a documented no-op so the output frame
listener wiring is unchanged.
- Deduplicate forward declarations: tab_index (keep grouped decl) and
handle_xs_{associate,commit,surface_map} (keep fuller block).
- Drop empty #if HAVE_XWAYLAND/#endif around the plain Xwayland
comment above launch_client.
Verified with ./build.sh (only pre-existing sign-compare warnings
in thumb_extent_iter). No behavioural change intended.
Shutdown/teardown now reuses remove_tab instead of an open-coded free
loop, closing listener/thumb/MRU/timer gaps with less code; remove_tab
also destroys foreign-toplevel handles, and shutdown removes the full
standalone pointer listener set including gestures.
Key handling: delete the session-locked early return that swallowed
lock-screen typing (grabs stay swallowed by their chord handlers,
passive keys now forward; overlay gated centrally), and move Ctrl+Alt+Q
quit above the shortcuts inhibitor so VT switch and quit stay live
under inhibition (pure reorder).
Input: swallow pointer/touch during quit-confirm as well as overlay,
reset idle on scroll in both axis paths, and guard close_timer against
double-arm. Net fewer lines; all 176 smoke tests pass.
Preview menus open with a full-width header naming the active tab's current window (icon plus ellipsized title). Full width keeps menu sizing independent of the selection, so moving the highlight never resizes anything; thumbnail fill-share accounts for the header block within the existing margins. Taps on the header are swallowed.
Preview rows divide the content-sized menu (centered, 50px top/bottom margins) instead of using a fixed triple-title height, with each thumbnail capped at a quarter of the screen height. Measure, draw and hit-test share one metrics helper; snapshot size follows a cache with a legacy fallback. Adds a menu-band regression test.
Firefox paints into subsurfaces while its main surface stays transparent, so the single-buffer snapshot saw only black. Windows with subsurface content now composite at overlay-draw time from each live surface texture (which outlives current.buffer), while plain windows keep the legacy snapshot path. Adds wltest --subsurface repro mode and a regression test.
Probing every committed buffer with wlr_buffer_begin_data_ptr_access
first can stall on GPU fences at 60Hz, visibly slowing GPU clients
like alacritty when --overlay-previews is on. Check
wlr_buffer_get_dmabuf (a cheap attribute read with no mapping or
sync) first and lock dma-bufs directly for GPU import; only
non-dma-buf buffers go through the CPU snapshot path.
Snapshot thumbnails at 2x display size with box-averaged
downscaling instead of nearest-neighbor sampling, so window
content (especially terminal text) survives the shrink as
shapes rather than speckle. The GPU bilinear draw stage is
unchanged and now downsamples real detail. Solid colors
average to themselves, so existing color assertions hold.
Drop the ~15fps frame-done tick that woke hidden windows while
the preview overlay was held. Hidden clients now freeze on their
own (no scene frame callbacks), so their thumbnails sit at the
last-shown frame; the shown window keeps its thumbnail live
through its normal commits. Spontaneous hidden commits (a
window mapping mid-hold) still refresh via the unchanged
capture path.
Removes the preview timer, tree walk and arm/disarm logic, the
pulse smoke test that needed tick-driven hidden frames, and
rewrites the README/help wording to last-appeared semantics.
wltest --pulse stays for manual QA.
Adds a --overlay-previews flag that switches the hold-Alt tab
overlay from icon+title rows to icon plus a small live view of
each window (three times as tall as a title row, no captions).
Tabs with no mapped window keep their command-line text row.
Thumbnails stay live while the overlay is held: hidden windows
get no scene frame callbacks, so a ~15fps tick paces them with
frame-done (disarmed on release; the plain title list never
wakes background clients). Each commit is routed to its fresh
source -- a CPU snapshot for SHM attaches, a locked dma-buf
import for GPU clients, or the surface's own texture for
in-place SHM updates -- and hidden commits force a repaint.
Also adds a wltest --pulse MS mode (alternating color/black on
frame callbacks) as a deterministic live source, three smoke
tests (appear on open, map mid-hold, pulse flip), and README
docs.
On split display/render platforms (vc4 display + v3d render) the
GLES2 renderer resolves to the display node, which has no 3D engine
-- and wlroots then advertises that node as the dmabuf main device,
so Mesa clients try to render there and fall back to llvmpipe (mpv
goes software, drops hundreds of frames). Discover a real render
node under /dev/dri and override the advertised main device with it;
rendering, allocation and scanout paths are untouched, and a missing
node falls back to the previous renderer-derived behavior.
Park the hardware cursor plane while the overlay is held and paint
the themed cursor image into the plane buffer after the menu, so the
stack is cursor, overlay, content by construction on every backend:
no cursor layer exists for the menu layer to trade planes with, and
the menu buffer carries its own cursor wherever it lands. On the
composite path re-add software cursors after the menu pass (a no-op
while a hardware cursor plane is active).
Extend the VT test with kernel-side placement (debugfs crtc-pos and
normalized-zpos must match the logged box on top) and a single-snapshot
cursor-above-menu order check.
Delete the content hash, painted-tracking, loop poll checks and
composite-next cover: the render paths already paint the overlay on
every rendered frame while held, so change detection was pure
overhead -- and polling from the frame handler wedged dispatch at
100% CPU. Input bindings that mutate the visible menu (tab reorder,
member move, Alt+Shift+T) end in whole-damage plus redraw like any
other keybinding; everything else rides pre-existing commit, damage
and transition flows. Keeps the persistent plane layer, probe with
sticky fallback, cursor parking during holds, and the per-frame
plane verdict log.
Replace the per-frame overlay layer churn with one persistent layer
per output (NULL buffer parks it), so libliftoff allocations settle
instead of re-solving around a fresh layer object every frame.
Repaint the held overlay from a per-frame content hash checked in the
main-loop bodies: any drift schedules a frame, clean walks cost one
cheap string pass. Deliberately not from the output frame handler --
scheduling there re-emits the same event via idle and wedges the loop
at 100% CPU with dead stdin (caught via gdb). Drop all scattered
explicit overlay refresh calls; the confirm-quit modal stays on its
explicit damage path.
Cover transient plane rejects with one composite frame then retry the
plane; lock software cursors while the overlay is held on DRM so the
menu layer can never trade planes with the cursor across re-solves.
Log the plane verdict and box (WxH+X+Y) every plane frame as the
off-process handle for VT verification.
Add tests/test_vt_plane.py: foot + Alt-held probe + Alt+Shift+T
alacritty on the real DRM backend, asserting the layer stays accepted
and the menu FB sits topmost at the logged geometry (libdrm plane
reads plus debugfs crtc-pos/zpos), with cursor warps and held-key
repeats churning the allocation.
On DRM standalone outputs the Alt tab list / confirm-quit modal is
scanned out on a hardware plane via wlr_output_layer (libliftoff), so
the primary buffer stays untouched and direct scanout of the focused
client keeps running underneath. Plane capability is probed once per
output from the first overlay commit and forced via
WLR_DRM_FORCE_LIBLIFTOFF; KICK_OVERLAY_PLANE=0 keeps the composite
fallback. Unlock the plane buffer only after commit, destroy the
per-frame layer after state finish, and recompute the attach-render
lock from capability on output recreate.
Overlay freshness no longer depends on scattered explicit refresh
calls: while held, each frame decision point hashes what the overlay
would draw (rows, titles, highlight, icons) and repaints on drift.
The confirm-quit modal stays on its explicit damage path.
Adds test_alt_shift_t_refreshes_held_overlay.
Coalesce nested renders while a host present is outstanding (send
frame-done only) instead of burning a full scene build plus GPU blit
per hold-repeat tick; flush clients after every nested render like the
standalone loop; size new/maximized windows and the layer layout from
the synchronously-updated host canvas rather than the lazily-committed
output object; warn when nesting in another kick with the same Alt
prefix; add debug-only render-stage timing.
A render queues frame-done (and other) events for clients; without a
flush before blocking in poll they sat in the connection until the
next wakeup (e.g. the headless 60Hz timer ~16ms later), visibly
slowing every standalone client -- including a nested kick, whose
swaps pace on frame-done. The nested loop already flushes every
iteration; mirror that here.
Also log the per-frame [diag] buffer line in render_standalone for
parity with the nested path (debug level only).
Reverts 01fd76e. Tested against the real jittery hardware it was meant
to steady and it did not help, so out it goes rather than carrying a
knob nobody can tune into working. Overlay wheel cycling returns to
one step per discrete tick; the smooth accumulator is untouched.
This reverts both the compositor change and its test plus the
_nested_session extra_env knob, which nothing else uses.
A bouncy old wheel emits a burst of discrete ticks per physical notch,
and each tick stepped one overlay tab, so the selection jumped around.
Discrete ticks now accumulate toward a per-step threshold with net
remainders (a +2/-1 burst leaves +1 instead of jumping), mirroring the
smooth-scroll accumulator: deliberate flicks still skip multiple tabs.
The threshold is KICK_WHEEL_TICKS, read per event like
KICK_REPEAT_DELAY_MS; unset or invalid means 1, today's
one-notch-per-step, so good hardware sees no change. It only affects
overlay cycling, never client scrolling, and the leftover fraction
resets when the overlay hides. The smooth 15px threshold is untouched.
Covered by test_overlay_wheel_ticks_env, which spawns with
KICK_WHEEL_TICKS=3 (via a new _nested_session extra_env knob) and
proves two ticks step nowhere while three step exactly once; it fails
on unpatched code.
One wheel notch in standalone did nothing with the overlay up: libinput
reports discrete ticks scaled by WLR_POINTER_AXIS_DISCRETE_STEP (x120),
so a single notch ran cycle_tab 120 times -- an even number of wraps on
two tabs, netting zero visible movement while still swallowing the
event. The standalone axis handler now divides by the macro before
calling overlay_handle_axis; the client forward path stays raw since
the wlroots seat divides it back itself. Nested was never affected
(its discrete values already arrive in wire units).
Proven by test_standalone_overlay_wheel_libinput_scale, which drives
one notch through the real standalone handler via a new wltest
--virtual-axis mode and asserts exactly one step (it fails with 120
steps without the fix). wltest also grows the missing
axis_source/stop/discrete pointer handlers, without which libwayland
aborts any test client forwarded a scroll event, plus axis lines in
--pointer-out.
Rolling the wheel while the overlay is up now steps through tabs
exactly like Alt+Up/Down: wheel down goes to the next tab, wheel up
to the previous one, wrapping at the ends, previewed live and
committed when Alt is released. Notched wheels step one tab per tick;
smooth touchpad-style scroll accumulates to 15px per step. Horizontal
scroll still goes to clients, and with the overlay down nothing
changes.
Swallowed vertical scroll never reaches clients (both the nested and
standalone axis paths share one overlay_handle_axis gate); wltest
grows axis_source/stop/discrete handlers so forwarded scroll no
longer aborts test clients, plus axis lines in --pointer-out.
Moving the cursor over an overlay row now previews that window, the
same as dragging a finger across it; sliding off every row keeps the
last preview. Clicks while the overlay is up do nothing and never
reach clients, and releasing Alt commits the preview.