Cleanup X11 momentum-scroll

Dont cancel on the synthetic key events x11 generates for scrolling.
Also use correct timestamp for duration and expiry of physical events.
This commit is contained in:
Kovid Goyal 2026-01-26 12:40:42 +05:30
parent cac2c6d2f9
commit d0249c1e72
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
3 changed files with 37 additions and 23 deletions

5
glfw/internal.h vendored
View file

@ -887,6 +887,11 @@ MonitorGeometry _glfwPlatformGetMonitorGeometry(_GLFWmonitor* monitor);
bool _glfwPlatformGrabKeyboard(bool grab);
void glfw_handle_scroll_event_for_momentum(_GLFWwindow *w, const GLFWScrollEvent *ev, bool stopped, bool is_finger_based);
#define glfw_cancel_momentum_scroll() glfw_handle_scroll_event_for_momentum(NULL, NULL, false, false)
#ifdef _GLFW_X11
#define momentum_scroll_gesture_detection_timeout_ms 50
#else
#define momentum_scroll_gesture_detection_timeout_ms 0
#endif
char* _glfw_strdup(const char* source);

View file

@ -147,6 +147,7 @@ send_momentum_event(bool is_start) {
m = GLFW_MOMENTUM_PHASE_ENDED;
if (s.timer_id) glfwRemoveTimer(s.timer_id);
s.timer_id = 0;
s.state = NONE;
}
GLFWScrollEvent e = {
.offset_type=GLFW_SCROLL_OFFEST_HIGHRES, .momentum_type=m, .unscaled.x=s.velocity.x, .unscaled.y=s.velocity.y,
@ -179,12 +180,14 @@ void
glfw_handle_scroll_event_for_momentum(
_GLFWwindow *w, const GLFWScrollEvent *ev, bool stopped, bool is_finger_based
) {
const bool is_synthetic_momentum_start_event = stopped && momentum_scroll_gesture_detection_timeout_ms;
if (!w) { cancel_existing_scroll(true); return; }
if (!is_finger_based || ev->offset_type != GLFW_SCROLL_OFFEST_HIGHRES || s.friction < 0 || s.friction >= 1) {
_glfwInputScroll(w, ev);
return;
}
monotonic_t now = monotonic();
if (is_synthetic_momentum_start_event) now -= ms_to_monotonic_t(momentum_scroll_gesture_detection_timeout_ms);
if (s.state == PHYSICAL_EVENT_IN_PROGRESS) {
s.physical_event.displacement.x += ev->unscaled.x;
s.physical_event.displacement.y += ev->unscaled.y;
@ -200,13 +203,15 @@ glfw_handle_scroll_event_for_momentum(
else if (ev->unscaled.x > 0) s.scale = ev->x_offset / ev->unscaled.x;
if (s.window_id && s.window_id != w->id) cancel_existing_scroll(true);
if (s.state != PHYSICAL_EVENT_IN_PROGRESS) cancel_existing_scroll(false);
// Check for change in direction
double ldx, ldy; last_sample_delta(&ldx, &ldy);
if (ldx * ev->x_offset < 0 || ldy * ev->y_offset < 0) cancel_existing_scroll(true);
if (!is_synthetic_momentum_start_event) {
// Check for change in direction
double ldx, ldy; last_sample_delta(&ldx, &ldy);
if (ldx * ev->x_offset < 0 || ldy * ev->y_offset < 0) cancel_existing_scroll(true);
}
s.window_id = w->id;
s.keyboard_modifiers = ev->keyboard_modifiers;
if (ev->offset_type == GLFW_SCROLL_OFFEST_HIGHRES) {
add_sample(ev->unscaled.x, ev->unscaled.y, now);
if (!is_synthetic_momentum_start_event) add_sample(ev->unscaled.x, ev->unscaled.y, now);
if (stopped) s.state = is_suitable_for_momentum() ? MOMENTUM_IN_PROGRESS : NONE;
else s.state = PHYSICAL_EVENT_IN_PROGRESS;
} else {

42
glfw/x11_window.c vendored
View file

@ -52,7 +52,6 @@
static struct {
unsigned long long timer_id;
GLFWid window_id;
bool is_finger_based;
GLFWScrollEvent last_event;
} x11_momentum_scroll_state = {0};
@ -61,13 +60,13 @@ x11_scroll_stop_timer_callback(unsigned long long timer_id UNUSED, void *data UN
x11_momentum_scroll_state.timer_id = 0;
_GLFWwindow *w = _glfwWindowForId(x11_momentum_scroll_state.window_id);
if (w) {
glfw_handle_scroll_event_for_momentum(
w, &x11_momentum_scroll_state.last_event, true, x11_momentum_scroll_state.is_finger_based);
x11_momentum_scroll_state.last_event.y_offset = 0; x11_momentum_scroll_state.last_event.x_offset = 0;
x11_momentum_scroll_state.last_event.unscaled.x = 0; x11_momentum_scroll_state.last_event.unscaled.y = 0;
glfw_handle_scroll_event_for_momentum(w, &x11_momentum_scroll_state.last_event, true, true);
} else {
// Window no longer exists, cancel any ongoing momentum
glfw_cancel_momentum_scroll();
}
x11_momentum_scroll_state.window_id = 0;
}
static void
@ -1375,16 +1374,16 @@ handle_xi_motion_event(_GLFWwindow *window, XIDeviceEvent *de) {
if (d->is_highres && d->is_finger_based && type == GLFW_SCROLL_OFFEST_HIGHRES) {
// Reset the timer on each scroll event
x11_cancel_momentum_scroll_timer();
// Store the event for later use when timer fires
x11_momentum_scroll_state.window_id = window->id;
x11_momentum_scroll_state.is_finger_based = true;
x11_momentum_scroll_state.last_event = ev;
// Start timer (100ms after last scroll event)
// Start timer
x11_momentum_scroll_state.timer_id = glfwAddTimer(
ms_to_monotonic_t(100), false, x11_scroll_stop_timer_callback, NULL, NULL);
ms_to_monotonic_t(momentum_scroll_gesture_detection_timeout_ms), false,
x11_scroll_stop_timer_callback, NULL, NULL);
// Send the scroll event through momentum handler
glfw_handle_scroll_event_for_momentum(window, &ev, false, true);
} else {
@ -1613,16 +1612,18 @@ static void processEvent(XEvent *event)
{
const int mods = translateState(event->xbutton.state);
// Cancel momentum scrolling on any button press
x11_cancel_momentum_scroll_timer();
glfw_cancel_momentum_scroll();
#define cancel_momentum() x11_cancel_momentum_scroll_timer(); glfw_cancel_momentum_scroll()
if (event->xbutton.button == Button1)
if (event->xbutton.button == Button1) {
cancel_momentum();
_glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS, mods);
else if (event->xbutton.button == Button2)
} else if (event->xbutton.button == Button2) {
cancel_momentum();
_glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS, mods);
else if (event->xbutton.button == Button3)
} else if (event->xbutton.button == Button3) {
cancel_momentum();
_glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS, mods);
}
// Modern X provides scroll events as mouse button presses
// Only use these if smooth scrolling is not available
@ -1649,6 +1650,7 @@ static void processEvent(XEvent *event)
else
{
cancel_momentum();
// Additional buttons after 7 are treated as regular buttons
// We subtract 4 to fill the gap left by scroll input above
_glfwInputMouseClick(window,
@ -1664,12 +1666,10 @@ static void processEvent(XEvent *event)
{
const int mods = translateState(event->xbutton.state);
// Cancel momentum scrolling on any button release
x11_cancel_momentum_scroll_timer();
glfw_cancel_momentum_scroll();
if (event->xbutton.button == Button1)
{
cancel_momentum();
_glfwInputMouseClick(window,
GLFW_MOUSE_BUTTON_LEFT,
GLFW_RELEASE,
@ -1677,6 +1677,7 @@ static void processEvent(XEvent *event)
}
else if (event->xbutton.button == Button2)
{
cancel_momentum();
_glfwInputMouseClick(window,
GLFW_MOUSE_BUTTON_MIDDLE,
GLFW_RELEASE,
@ -1684,6 +1685,7 @@ static void processEvent(XEvent *event)
}
else if (event->xbutton.button == Button3)
{
cancel_momentum();
_glfwInputMouseClick(window,
GLFW_MOUSE_BUTTON_RIGHT,
GLFW_RELEASE,
@ -1691,6 +1693,7 @@ static void processEvent(XEvent *event)
}
else if (event->xbutton.button > Button7)
{
cancel_momentum();
// Additional buttons after 7 are treated as regular buttons
// We subtract 4 to fill the gap left by scroll input above
_glfwInputMouseClick(window,
@ -1700,6 +1703,7 @@ static void processEvent(XEvent *event)
}
return;
#undef cancel_momentum
}
case EnterNotify: