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.
A leaked WLR_BACKENDS (e.g. drm,libinput) or KICK_MODE in the ambient environment flips spawned nested compositors to standalone, where the DRM backend fails and every test dies at boot for reasons unrelated to what it asserts. Seen when running the suite from a shell carrying the agent session's own WLR_BACKENDS export. Strip just those two (host connection and socket dir must pass through); precedent: kick-in-kick pops and standalone env -i.
Every tab is maximized and kick draws no frames, so new toplevels
are pinned to client-side mode (headerbars keep working) and later
server-side requests are ignored. Pinning happens at map -- set_mode
asserts on a never-committed surface, and decoration-before-commit is
the normal order (calling it in new_toplevel_decoration aborts
wlroots); late binds pin immediately when already initialized.
Adds an xdeco probe (maps one shm-backed toplevel, reports the
configured mode) with a regression test asserting mode=1, tracks it
in build.sh staleness, and covers the global in protocol_globals.
Full suite: 153 passed, 2 skipped.
All 16 assert _wait_log(s, rx, timeout) sites become s.wait_for(rx,
timeout); every caller only asserted truthiness, and both poll the
whole log identically, so behavior is unchanged (failures now report
the pattern plus a log tail instead of a custom one-liner). Sites on
_wait_log's 15s default keep timeout=15.0 explicitly since wait_for
defaults to 20s. The helper is deleted. Full suite: 152 passed,
2 skipped.