From 22a4b135f0a52164a98d4a988115acaa7d32fdc3 Mon Sep 17 00:00:00 2001 From: neon12345 <784389+neon12345@users.noreply.github.com> Date: Thu, 16 Jul 2026 21:18:42 +0200 Subject: [PATCH] Accumulate smooth scroll into full wheel clicks on server MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(platform/ei): accumulate smooth scroll into full wheel clicks on server On Wayland, two-finger smooth scroll events are delivered as per-frame pixel deltas (e.g. 0.26, 0.53, 1.05 px per event). The previous code quantized these at the pixel level via `modf()` — any accumulated integer pixel (even just 1) triggered a wheel event whose value was `pixels * 12`, i.e. 12, 24 or 36 units. Because the deskflow protocol uses 120 units per standard wheel click, these fractional clicks were silently dropped by the client compositor and the user saw nothing on the secondary screen until a very large/fast stroke pushed enough pixels through. Switch to wheel-click-level accumulation: smooth scroll pixel deltas are now scaled to fractional wheel-clicks (0.1 per pixel, keeping the 10 px == 1 click convention) and only dispatched to the client when at least one full 120-unit click has accumulated. This uses `std::trunc()` (toward zero) instead of `std::floor()` to avoid spurious clicks on negative scroll values. Physical mouse wheels (discrete scroll events, EI_EVENT_SCROLL_DISCRETE) are unaffected and continue to be forwarded immediately as-is. - `EiScreen.cpp`: replace `modf()` pixel quantization with fractional wheel-click accumulation using `std::trunc()`. - `ScrollRemainder` struct comment: clarify the unit is now fractional wheel-clicks rather than pixels. --- src/lib/platform/EiScreen.cpp | 45 ++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/src/lib/platform/EiScreen.cpp b/src/lib/platform/EiScreen.cpp index 585d186aa..c2569d5cd 100644 --- a/src/lib/platform/EiScreen.cpp +++ b/src/lib/platform/EiScreen.cpp @@ -28,7 +28,7 @@ #include #include -// Values are in pixels +// Values are in fractional wheel-click units (1.0 == one full 120-unit click) struct ScrollRemainder { double x; @@ -762,14 +762,12 @@ void EiScreen::onButtonEvent(ei_event *event) void EiScreen::onPointerScrollEvent(ei_event *event) { - // Ratio of 10 pixels == one wheel click because that's what mutter/gtk - // use (for historical reasons). - static const int s_pixelsPerWheelClick = 10; - // Our logical wheel clicks are multiples 120, so we - // convert between the two and keep the remainders because - // we will very likely get subpixel scroll events. - // This means a single pixel is 120/s_pixelToWheelRation in wheel values. - const int s_pixelToWheelRatio = s_scrollDelta / s_pixelsPerWheelClick; + // Smooth scroll deltas are in pixels. We accumulate them as fractional + // wheel-click units and only send full wheel clicks (120 units each) + // to the client. Sub-120 fractional clicks are silently ignored by + // compositors on the receiving end, so accumulating full clicks avoids + // flooding the network with events that the client drops anyway. + static const double s_wheelClicksPerPixel = 0.1; // 10 pixels == 1 full wheel click assert(m_isPrimary); @@ -785,28 +783,31 @@ void EiScreen::onPointerScrollEvent(ei_event *event) ei_device_set_user_data(device, remainder); } - dx += remainder->x; - dy += remainder->y; + // Accumulate smooth scroll as fractional wheel clicks (1.0 == 120 units) + double accX = remainder->x + dx * s_wheelClicksPerPixel; + double accY = remainder->y + dy * s_wheelClicksPerPixel; - double x; - double y; - double rx = modf(dx, &x); - double ry = modf(dy, &y); - - assert(!std::isnan(x) && !std::isinf(x)); - assert(!std::isnan(y) && !std::isinf(y)); + // Only dispatch full wheel clicks. Use trunc (toward zero) not floor, + // because floor(-0.3) == -1 which would fire a spurious click. + double fullClicksX = std::trunc(accX); + double fullClicksY = std::trunc(accY); // libei and deskflow seem to use opposite directions, so we have // to send the opposite of the value reported by EI if we want to // remain compatible with other platforms (including X11). - if (x != 0 || y != 0) + if (fullClicksX != 0 || fullClicksY != 0) { sendEvent( EventTypes::PrimaryScreenWheel, - WheelInfo::alloc((int32_t)-x * s_pixelToWheelRatio, (int32_t)-y * s_pixelToWheelRatio) + WheelInfo::alloc( + static_cast(-fullClicksX) * s_scrollDelta, static_cast(-fullClicksY) * s_scrollDelta + ) ); + accX -= fullClicksX; + accY -= fullClicksY; + } - remainder->x = rx; - remainder->y = ry; + remainder->x = accX; + remainder->y = accY; } void EiScreen::onPointerScrollDiscreteEvent(ei_event *event)