From 153c93633fa2b20118a168d844114fa6d2e332bf Mon Sep 17 00:00:00 2001 From: liguangsheng Date: Fri, 14 Aug 2026 18:55:02 +0800 Subject: [PATCH] X11: fix scroll wheel being ignored when spurious crossing events occur resetScrollValuators() runs on every LeaveNotify, clearing the initialized flag of every scroll valuator. handle_xi_motion_event() then treats the next XI_Motion event as a baseline sample and discards it. On setups where the X server emits crossing events with mode NotifyGrab faster than the wheel is turned, every scroll event lands in that branch and is swallowed, so scrolling does not work at all. This is easy to hit inside a VMware guest, where the same physical mouse is exposed as several pointer devices: --debug-input shows a continuous leave/enter cycle and a single Scroll event for the whole session. Fix this in two places: - Do not reset the baseline for crossing events generated by a grab being activated/released or by the pointer moving to a child window, since the pointer did not actually leave. - Keep the baseline value across a reset so the next event can still compute a delta from it, and only discard the event when the jump is too large to be a genuine scroll. Fixes #9846 --- glfw/x11_platform.h | 2 +- glfw/x11_window.c | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/glfw/x11_platform.h b/glfw/x11_platform.h index 5c1386b9f..363044951 100644 --- a/glfw/x11_platform.h +++ b/glfw/x11_platform.h @@ -246,7 +246,7 @@ typedef struct AtomArray { typedef struct XIScrollValuator { double increment, value, min, max; int number, resolution, mode; - bool is_vertical, initialized; + bool is_vertical, initialized, has_value; } XIScrollValuator; typedef struct XIScrollDevice { diff --git a/glfw/x11_window.c b/glfw/x11_window.c index d92ea5afe..01ac6c6b2 100644 --- a/glfw/x11_window.c +++ b/glfw/x11_window.c @@ -1308,11 +1308,20 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) { scroll_valuator_found = true; if (!v->initialized) { v->initialized = true; - v->value = value; - continue; + if (!v->has_value) { + v->has_value = true; + v->value = value; + continue; + } + const double max_plausible_delta = 10. * (v->increment != 0. ? fabs(v->increment) : 1.); + if (fabs(value - v->value) > max_plausible_delta) { + v->value = value; + continue; + } } double delta = value - v->value; v->value = value; + v->has_value = true; delta *= -1; double *off = v->is_vertical ? &yOffset : &xOffset; *off = delta; @@ -2018,7 +2027,7 @@ processEvent(XEvent *event) { } case LeaveNotify: { - resetScrollValuators(); + if (event->xcrossing.mode == NotifyNormal && event->xcrossing.detail != NotifyInferior) resetScrollValuators(); _glfwInputCursorEnter(window, false); return; }