From a532f655c194c683fc48ebcc467f56b97885d5a3 Mon Sep 17 00:00:00 2001 From: sithlord48 Date: Sun, 8 Feb 2026 10:36:54 -0500 Subject: [PATCH] feat: support horizontal scrolling fixes: #7220 based on https://github.com/debauchee/barrier/commit/9f15b1bcf2953acf609f7580463bf13cdb23d008 Co-authored-by: Evan Maddock --- src/lib/deskflow/MouseTypes.h | 2 + src/lib/deskflow/PlatformScreen.h | 3 ++ src/lib/platform/EiScreen.cpp | 2 +- src/lib/platform/MSWindowsDesks.cpp | 7 +++ src/lib/platform/MSWindowsHook.cpp | 18 ++++++-- src/lib/platform/MSWindowsScreen.cpp | 3 +- src/lib/platform/OSXScreen.mm | 6 ++- src/lib/platform/XWindowsScreen.cpp | 66 +++++++++++++++------------- 8 files changed, 69 insertions(+), 38 deletions(-) diff --git a/src/lib/deskflow/MouseTypes.h b/src/lib/deskflow/MouseTypes.h index 77cbbe8e0..160748fec 100644 --- a/src/lib/deskflow/MouseTypes.h +++ b/src/lib/deskflow/MouseTypes.h @@ -30,6 +30,8 @@ static const ButtonID kMacButtonMiddle = 3; // Only used for X11 Scroll Wheel static const ButtonID kX11ScrollWheelUp = 255; static const ButtonID kX11ScrollWheelDown = 254; +static const ButtonID kX11ScrollWheelLeft = 6; +static const ButtonID kX11ScrollWheelRight = 7; //@} static const uint8_t NumButtonIDs = 6; diff --git a/src/lib/deskflow/PlatformScreen.h b/src/lib/deskflow/PlatformScreen.h index 9dba6bf17..4a3b245c8 100644 --- a/src/lib/deskflow/PlatformScreen.h +++ b/src/lib/deskflow/PlatformScreen.h @@ -100,4 +100,7 @@ protected: Converts a sides mask (e.g. LeftMask | RightMask) to a string representation (e.g. "LR"). */ static std::string sidesMaskToString(uint32_t sides); + + // Delta for a "click" + static const auto s_scrollDelta = 120; }; diff --git a/src/lib/platform/EiScreen.cpp b/src/lib/platform/EiScreen.cpp index dbcad364d..5ef4c8202 100644 --- a/src/lib/platform/EiScreen.cpp +++ b/src/lib/platform/EiScreen.cpp @@ -672,7 +672,7 @@ void EiScreen::onPointerScrollEvent(ei_event *event) // 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 = 120 / s_pixelsPerWheelClick; + const int s_pixelToWheelRatio = s_scrollDelta / s_pixelsPerWheelClick; assert(m_isPrimary); diff --git a/src/lib/platform/MSWindowsDesks.cpp b/src/lib/platform/MSWindowsDesks.cpp index 37f339beb..a47b57aba 100644 --- a/src/lib/platform/MSWindowsDesks.cpp +++ b/src/lib/platform/MSWindowsDesks.cpp @@ -34,6 +34,10 @@ #define SPI_GETSCREENSAVERRUNNING 114 #endif +#if !defined(MOUSEEVENTF_HWHEEL) +#define MOUSEEVENTF_HWHEEL 0x1000 +#endif + // X button stuff #if !defined(WM_XBUTTONDOWN) #define WM_XBUTTONDOWN 0x020B @@ -697,6 +701,9 @@ void MSWindowsDesks::deskThread(const void *vdesk) if (msg.lParam != 0) { send_mouse_input(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam); } + if (msg.wParam != 0) { + send_mouse_input(MOUSEEVENTF_HWHEEL, 0, 0, (DWORD)msg.wParam); + } break; case DESKFLOW_MSG_CURSOR_POS: { diff --git a/src/lib/platform/MSWindowsHook.cpp b/src/lib/platform/MSWindowsHook.cpp index 152a2c391..c2ef1e1ea 100644 --- a/src/lib/platform/MSWindowsHook.cpp +++ b/src/lib/platform/MSWindowsHook.cpp @@ -10,6 +10,10 @@ #include "base/Log.h" #include "deskflow/ScreenException.h" +#ifndef WM_MOUSEHWHEEL +#define WM_MOUSEHWHEEL 0x020E +#endif + static const char *g_name = "dfwhook"; static DWORD g_processID = 0; @@ -505,6 +509,13 @@ static bool mouseHookHandler(WPARAM wParam, int32_t x, int32_t y, int32_t data) } return (g_mode == kHOOK_RELAY_EVENTS); + case WM_MOUSEHWHEEL: + if (g_mode == kHOOK_RELAY_EVENTS) { + // relay event + PostThreadMessage(g_threadID, DESKFLOW_MSG_MOUSE_WHEEL, 0, data); + } + return (g_mode == kHOOK_RELAY_EVENTS); + case WM_NCMOUSEMOVE: case WM_MOUSEMOVE: if (g_mode == kHOOK_RELAY_EVENTS) { @@ -620,9 +631,10 @@ EHookResult MSWindowsHook::install() } } #endif - + // clang-format off // check that we got all the hooks we wanted - if ((g_mouseLL == nullptr) || + if ( + (g_mouseLL == nullptr) || #if !NO_GRAB_KEYBOARD (g_keyboardLL == nullptr) #endif @@ -630,7 +642,7 @@ EHookResult MSWindowsHook::install() uninstall(); return kHOOK_FAILED; } - + // clang-format on if (g_keyboardLL != nullptr || g_mouseLL != nullptr) { g_hookThread = GetCurrentThreadId(); return kHOOK_OKAY_LL; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index fcd528b59..f2f5a1646 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -897,8 +897,7 @@ bool MSWindowsScreen::onPreDispatchPrimary(HWND, UINT message, WPARAM wParam, LP return onMouseMove(static_cast(wParam), static_cast(lParam)); case DESKFLOW_MSG_MOUSE_WHEEL: - // XXX -- support x-axis scrolling - return onMouseWheel(0, static_cast(wParam)); + return onMouseWheel(static_cast(lParam), static_cast(wParam)); case DESKFLOW_MSG_PRE_WARP: { // save position to compute delta of next motion diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index 2bd00d2f0..32d23273e 100644 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -400,6 +400,7 @@ void OSXScreen::constructMouseButtonEventMap() {kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown}, {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, + {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown} }; @@ -589,8 +590,9 @@ void OSXScreen::fakeMouseWheel(ScrollDelta delta) const if (delta.x != 0 || delta.y != 0) { // use server's acceleration with a little boost since other platforms // take one wheel step as a larger step than the mac does. - delta = - applyScrollModifier({static_cast(3.0 * delta.x / 120.0), static_cast(3.0 * delta.y / 120.0)}); + delta = applyScrollModifier( + {static_cast(3.0 * delta.x / s_scrollDelta), static_cast(3.0 * delta.y / s_scrollDelta)} + ); // create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine // is the right choice here over kCGScrollEventUnitPixel CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, delta.y, delta.x); diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index f4c33fa24..d8c7bb94d 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -786,44 +786,39 @@ void XWindowsScreen::fakeMouseRelativeMove(int32_t dx, int32_t dy) const void XWindowsScreen::fakeMouseWheel(ScrollDelta delta) const { - // XXX -- support x-axis scrolling - if (delta.y == 0) { + if (delta.y == 0 && delta.x == 0) { return; } - delta = applyScrollModifier(delta); + bool isYScroll = delta.y != 0; + int32_t axisDelta = delta.y == 0 ? delta.x : delta.y; - // choose button depending on rotation direction - const unsigned int xButton = mapButtonToX(delta.y >= 0 ? kX11ScrollWheelUp : kX11ScrollWheelDown); - if (xButton == 0) { - // If we get here, then the XServer does not support the scroll - // wheel buttons, so send PageUp/PageDown keystrokes instead. - // Patch by Tom Chadwick. - KeyCode keycode = 0; - if (delta.y >= 0) { - keycode = XKeysymToKeycode(m_display, XK_Page_Up); - } else { - keycode = XKeysymToKeycode(m_display, XK_Page_Down); - } - if (keycode != 0) { - XTestFakeKeyEvent(m_display, keycode, True, CurrentTime); - XTestFakeKeyEvent(m_display, keycode, False, CurrentTime); - } + ButtonID btn = kButtonNone; + if (isYScroll) { + btn = axisDelta >= 0 ? kX11ScrollWheelUp : kX11ScrollWheelDown; + } else { + btn = axisDelta >= 0 ? kX11ScrollWheelLeft : kX11ScrollWheelRight; + } + const unsigned int button = mapButtonToX(btn); + + // If the XServer does not support the Y scroll send PageUp/PageDown keystrokes instead. + if (const KeyCode keycode = XKeysymToKeycode(m_display, delta.y >= 0 ? XK_Page_Up : XK_Page_Down); + button == 0 && isYScroll && keycode != 0) { + XTestFakeKeyEvent(m_display, keycode, True, CurrentTime); + XTestFakeKeyEvent(m_display, keycode, False, CurrentTime); return; } // now use absolute value of delta - if (delta.y < 0) { - delta.y = -delta.y; + if (axisDelta < 0) { + axisDelta = -axisDelta; } - // Delta for a "click" - static const auto s_mouseDelta = 120; - // send as many clicks as necessary - for (; delta.y >= 0; delta.y -= s_mouseDelta) { - XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); - XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); + while (axisDelta >= 0) { + XTestFakeButtonEvent(m_display, button, True, CurrentTime); + XTestFakeButtonEvent(m_display, button, False, CurrentTime); + axisDelta -= s_scrollDelta; } XFlush(m_display); } @@ -1461,12 +1456,17 @@ void XWindowsScreen::onMouseRelease(const XButtonEvent &xbutton) sendEvent(PrimaryScreenButtonUp, ButtonInfo::alloc(button, mask)); } else if (xbutton.button == 4) { // wheel forward (away from user) - sendEvent(PrimaryScreenWheel, WheelInfo::alloc(0, 120)); + sendEvent(PrimaryScreenWheel, WheelInfo::alloc(0, s_scrollDelta)); } else if (xbutton.button == 5) { // wheel backward (toward user) - sendEvent(PrimaryScreenWheel, WheelInfo::alloc(0, -120)); + sendEvent(PrimaryScreenWheel, WheelInfo::alloc(0, -s_scrollDelta)); + } else if (xbutton.button == 6) { + // wheel tilt left + sendEvent(PrimaryScreenWheel, WheelInfo::alloc(-s_scrollDelta, 0)); + } else if (xbutton.button == 7) { + // wheel tilt right + sendEvent(PrimaryScreenWheel, WheelInfo::alloc(s_scrollDelta, 0)); } - // XXX -- support x-axis scrolling } void XWindowsScreen::onMouseMove(const XMotionEvent &xmotion) @@ -1740,6 +1740,8 @@ ButtonID XWindowsScreen::mapButtonFromX(const XButtonEvent *event) const case 1: case 2: case 3: + case 6: + case 7: return static_cast(button); // Handle Left, Middle and Right buttons case 8: return kButtonExtra0; // Mouse 4 @@ -1761,6 +1763,10 @@ unsigned int XWindowsScreen::mapButtonToX(ButtonID id) const return 4; case kX11ScrollWheelDown: return 5; + case kX11ScrollWheelLeft: + return 6; + case kX11ScrollWheelRight: + return 7; case kButtonExtra0: return 8; case kButtonExtra1: