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
This commit is contained in:
liguangsheng 2026-08-14 18:55:02 +08:00
parent b71ef95ec7
commit 153c93633f
2 changed files with 13 additions and 4 deletions

2
glfw/x11_platform.h vendored
View file

@ -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 {

15
glfw/x11_window.c vendored
View file

@ -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;
}