fix: normalize libinput wheel scale so standalone overlay cycling works
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.
This commit is contained in:
parent
0b9012cd6f
commit
3a6fdd4fd4
3 changed files with 109 additions and 1 deletions
9
kick.c
9
kick.c
|
|
@ -10694,7 +10694,14 @@ static void standalone_ptr_axis_event(struct wl_listener *listener, void *data)
|
||||||
(void)listener;
|
(void)listener;
|
||||||
struct wlr_pointer_axis_event *ev = data;
|
struct wlr_pointer_axis_event *ev = data;
|
||||||
if (!server.seat) return;
|
if (!server.seat) return;
|
||||||
if (overlay_handle_axis(ev->orientation, ev->delta, ev->delta_discrete))
|
/* libinput scales discrete steps by WLR_POINTER_AXIS_DISCRETE_STEP
|
||||||
|
(±120 per notch; the seat divides it back off for clients), while
|
||||||
|
overlay_handle_axis speaks Wayland wire units (±1). Normalize only
|
||||||
|
for the overlay call -- the client forward below must stay raw.
|
||||||
|
Sub-notch fractions truncate to 0 and fall through to the smooth
|
||||||
|
accumulator inside the helper. */
|
||||||
|
if (overlay_handle_axis(ev->orientation, ev->delta,
|
||||||
|
ev->delta_discrete / WLR_POINTER_AXIS_DISCRETE_STEP))
|
||||||
return;
|
return;
|
||||||
wlr_seat_pointer_notify_axis(server.seat, ev->time_msec, ev->orientation,
|
wlr_seat_pointer_notify_axis(server.seat, ev->time_msec, ev->orientation,
|
||||||
ev->delta, ev->delta_discrete, ev->source,
|
ev->delta, ev->delta_discrete, ev->source,
|
||||||
|
|
|
||||||
|
|
@ -5130,6 +5130,41 @@ def test_overlay_wheel_swallowed_by_client(tmp_path):
|
||||||
s.mods(0)
|
s.mods(0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_standalone_overlay_wheel_libinput_scale():
|
||||||
|
"""A libinput-scale discrete tick through the real standalone axis
|
||||||
|
handler steps exactly one tab -- the ×120 must be normalized, or one
|
||||||
|
notch cycles 120 times (net zero on two tabs) and the wheel looks
|
||||||
|
dead. Uses a virtual pointer, which feeds the same
|
||||||
|
standalone_ptr_axis_event libinput does (wlroots scales the wire
|
||||||
|
discrete itself, so discrete=1 arrives like one real notch)."""
|
||||||
|
with _run_standalone(
|
||||||
|
{}, "%s '#ff0000'" % WL, "%s '#0000ff'" % WL, wait="any") as s:
|
||||||
|
s.wait_surface_mapped(0)
|
||||||
|
s.wait_surface_mapped(1)
|
||||||
|
start = s.active()
|
||||||
|
other = 0 if start != 0 else 1
|
||||||
|
s.mods(ALT)
|
||||||
|
try:
|
||||||
|
time.sleep(0.7)
|
||||||
|
n0 = len(re.findall(r"\[wheel\] cycle", s.read_log()))
|
||||||
|
_oneshot_virtual_axis(s.socket, 0, 15, 1)
|
||||||
|
deadline = time.monotonic() + 10.0
|
||||||
|
while time.monotonic() < deadline:
|
||||||
|
if len(re.findall(r"\[wheel\] cycle", s.read_log())) > n0:
|
||||||
|
break
|
||||||
|
time.sleep(0.05)
|
||||||
|
else:
|
||||||
|
pytest.fail("libinput-scale wheel tick never stepped a tab")
|
||||||
|
n1 = len(re.findall(r"\[wheel\] cycle", s.read_log()))
|
||||||
|
assert n1 == n0 + 1, \
|
||||||
|
f"one notch stepped {n1 - n0}x, want exactly 1"
|
||||||
|
s.wait_active(other, timeout=10.0)
|
||||||
|
finally:
|
||||||
|
s.mods(0)
|
||||||
|
s.wait_for(r"\[mru\] commit app=%d" % other, timeout=10.0)
|
||||||
|
assert s.active() == other
|
||||||
|
|
||||||
|
|
||||||
def test_synth_feed_drives_host_listeners(tmp_path):
|
def test_synth_feed_drives_host_listeners(tmp_path):
|
||||||
"""Synthesized host pointer/touch events go through the real host
|
"""Synthesized host pointer/touch events go through the real host
|
||||||
wl_pointer/wl_touch listeners instead of bypassing them for the shared
|
wl_pointer/wl_touch listeners instead of bypassing them for the shared
|
||||||
|
|
@ -5864,6 +5899,23 @@ def _oneshot_virtual_abs(sock, x, y, click=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _oneshot_virtual_axis(sock, axis, value, discrete):
|
||||||
|
"""Run a one-shot --virtual-axis (scroll) against a live socket."""
|
||||||
|
p = subprocess.Popen(
|
||||||
|
[WL, "--virtual-axis", str(axis), str(value), str(discrete)],
|
||||||
|
env=dict(os.environ, WAYLAND_DISPLAY=sock),
|
||||||
|
stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL, start_new_session=True,
|
||||||
|
preexec_fn=_preexec_death(os.getpid()))
|
||||||
|
try:
|
||||||
|
p.wait(timeout=10.0)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
p.kill()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_standalone_headless_virtual_pointer_motion_click(tmp_path):
|
def test_standalone_headless_virtual_pointer_motion_click(tmp_path):
|
||||||
"""A virtual pointer moves the cursor and clicks: the focused window
|
"""A virtual pointer moves the cursor and clicks: the focused window
|
||||||
receives pointer motion and button events through the seat."""
|
receives pointer motion and button events through the seat."""
|
||||||
|
|
|
||||||
|
|
@ -2869,6 +2869,40 @@ static double virtual_dx, virtual_dy;
|
||||||
static double virtual_abs_x = -1.0, virtual_abs_y = -1.0;
|
static double virtual_abs_x = -1.0, virtual_abs_y = -1.0;
|
||||||
static bool virtual_click;
|
static bool virtual_click;
|
||||||
|
|
||||||
|
/* --virtual-axis <axis> <value> <discrete>: send one scroll event through
|
||||||
|
a virtual pointer (axis 0 = vertical). Values are client wire units:
|
||||||
|
wlroots scales discrete by WLR_POINTER_AXIS_DISCRETE_STEP itself, so
|
||||||
|
discrete=1 arrives at the compositor exactly like one libinput wheel
|
||||||
|
notch. Exits after flushing. */
|
||||||
|
static bool virtual_axis_set;
|
||||||
|
static uint32_t virtual_axis;
|
||||||
|
static double virtual_axis_value;
|
||||||
|
static int32_t virtual_axis_discrete;
|
||||||
|
|
||||||
|
static void virtual_axis_run(void) {
|
||||||
|
display = wl_display_connect(NULL);
|
||||||
|
if (!display) {
|
||||||
|
fprintf(stderr, "wltest: virtual-axis: cannot connect\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
struct wl_registry *reg = wl_display_get_registry(display);
|
||||||
|
wl_registry_add_listener(reg, ®istry_listener, NULL);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
if (!virtual_ptr_mgr || !seat) {
|
||||||
|
fprintf(stderr, "wltest: virtual-axis: manager or seat missing\n");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
struct zwlr_virtual_pointer_v1 *vptr =
|
||||||
|
zwlr_virtual_pointer_manager_v1_create_virtual_pointer(
|
||||||
|
virtual_ptr_mgr, seat);
|
||||||
|
zwlr_virtual_pointer_v1_axis(vptr, 1000, virtual_axis,
|
||||||
|
wl_fixed_from_double(virtual_axis_value));
|
||||||
|
zwlr_virtual_pointer_v1_axis_discrete(vptr, 1000, virtual_axis,
|
||||||
|
wl_fixed_from_double(virtual_axis_value), virtual_axis_discrete);
|
||||||
|
zwlr_virtual_pointer_v1_frame(vptr);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
}
|
||||||
|
|
||||||
static void virtual_motion_run(void) {
|
static void virtual_motion_run(void) {
|
||||||
display = wl_display_connect(NULL);
|
display = wl_display_connect(NULL);
|
||||||
if (!display) {
|
if (!display) {
|
||||||
|
|
@ -4091,6 +4125,16 @@ int main(int argc, char **argv) {
|
||||||
virtual_abs_x = atof(argv[i + 1]);
|
virtual_abs_x = atof(argv[i + 1]);
|
||||||
virtual_abs_y = atof(argv[i + 2]);
|
virtual_abs_y = atof(argv[i + 2]);
|
||||||
i += 3;
|
i += 3;
|
||||||
|
} else if (strcmp(argv[i], "--virtual-axis") == 0) {
|
||||||
|
if (i + 3 >= argc) {
|
||||||
|
fprintf(stderr, "wltest: --virtual-axis needs axis value discrete\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
virtual_axis = (uint32_t)strtoul(argv[i + 1], NULL, 0);
|
||||||
|
virtual_axis_value = atof(argv[i + 2]);
|
||||||
|
virtual_axis_discrete = (int32_t)strtol(argv[i + 3], NULL, 0);
|
||||||
|
virtual_axis_set = true;
|
||||||
|
i += 4;
|
||||||
} else if (strcmp(argv[i], "--virtual-modifiers") == 0) {
|
} else if (strcmp(argv[i], "--virtual-modifiers") == 0) {
|
||||||
if (i + 1 >= argc) {
|
if (i + 1 >= argc) {
|
||||||
fprintf(stderr, "wltest: --virtual-modifiers needs a mask\n");
|
fprintf(stderr, "wltest: --virtual-modifiers needs a mask\n");
|
||||||
|
|
@ -4256,6 +4300,11 @@ int main(int argc, char **argv) {
|
||||||
virtual_motion_run();
|
virtual_motion_run();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
/* Virtual pointer scroll: --virtual-axis sends one axis event. */
|
||||||
|
if (virtual_axis_set) {
|
||||||
|
virtual_axis_run();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
/* Output-power mode: blank/unblank the output, then exit. */
|
/* Output-power mode: blank/unblank the output, then exit. */
|
||||||
if (power_arg) {
|
if (power_arg) {
|
||||||
power_run();
|
power_run();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue