feat: support horizontal scrolling

fixes: #7220
based on 9f15b1bcf2
Co-authored-by: Evan Maddock <maddock.evan@vivaldi.net>
This commit is contained in:
sithlord48 2026-02-08 10:36:54 -05:00 committed by Nick Bolton
parent 82588a7fc9
commit a532f655c1
8 changed files with 69 additions and 38 deletions

View file

@ -30,6 +30,8 @@ static const ButtonID kMacButtonMiddle = 3;
// Only used for X11 Scroll Wheel // Only used for X11 Scroll Wheel
static const ButtonID kX11ScrollWheelUp = 255; static const ButtonID kX11ScrollWheelUp = 255;
static const ButtonID kX11ScrollWheelDown = 254; static const ButtonID kX11ScrollWheelDown = 254;
static const ButtonID kX11ScrollWheelLeft = 6;
static const ButtonID kX11ScrollWheelRight = 7;
//@} //@}
static const uint8_t NumButtonIDs = 6; static const uint8_t NumButtonIDs = 6;

View file

@ -100,4 +100,7 @@ protected:
Converts a sides mask (e.g. LeftMask | RightMask) to a string representation (e.g. "LR"). Converts a sides mask (e.g. LeftMask | RightMask) to a string representation (e.g. "LR").
*/ */
static std::string sidesMaskToString(uint32_t sides); static std::string sidesMaskToString(uint32_t sides);
// Delta for a "click"
static const auto s_scrollDelta = 120;
}; };

View file

@ -672,7 +672,7 @@ void EiScreen::onPointerScrollEvent(ei_event *event)
// convert between the two and keep the remainders because // convert between the two and keep the remainders because
// we will very likely get subpixel scroll events. // we will very likely get subpixel scroll events.
// This means a single pixel is 120/s_pixelToWheelRation in wheel values. // 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); assert(m_isPrimary);

View file

@ -34,6 +34,10 @@
#define SPI_GETSCREENSAVERRUNNING 114 #define SPI_GETSCREENSAVERRUNNING 114
#endif #endif
#if !defined(MOUSEEVENTF_HWHEEL)
#define MOUSEEVENTF_HWHEEL 0x1000
#endif
// X button stuff // X button stuff
#if !defined(WM_XBUTTONDOWN) #if !defined(WM_XBUTTONDOWN)
#define WM_XBUTTONDOWN 0x020B #define WM_XBUTTONDOWN 0x020B
@ -697,6 +701,9 @@ void MSWindowsDesks::deskThread(const void *vdesk)
if (msg.lParam != 0) { if (msg.lParam != 0) {
send_mouse_input(MOUSEEVENTF_WHEEL, 0, 0, (DWORD)msg.lParam); 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; break;
case DESKFLOW_MSG_CURSOR_POS: { case DESKFLOW_MSG_CURSOR_POS: {

View file

@ -10,6 +10,10 @@
#include "base/Log.h" #include "base/Log.h"
#include "deskflow/ScreenException.h" #include "deskflow/ScreenException.h"
#ifndef WM_MOUSEHWHEEL
#define WM_MOUSEHWHEEL 0x020E
#endif
static const char *g_name = "dfwhook"; static const char *g_name = "dfwhook";
static DWORD g_processID = 0; 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); 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_NCMOUSEMOVE:
case WM_MOUSEMOVE: case WM_MOUSEMOVE:
if (g_mode == kHOOK_RELAY_EVENTS) { if (g_mode == kHOOK_RELAY_EVENTS) {
@ -620,9 +631,10 @@ EHookResult MSWindowsHook::install()
} }
} }
#endif #endif
// clang-format off
// check that we got all the hooks we wanted // check that we got all the hooks we wanted
if ((g_mouseLL == nullptr) || if (
(g_mouseLL == nullptr) ||
#if !NO_GRAB_KEYBOARD #if !NO_GRAB_KEYBOARD
(g_keyboardLL == nullptr) (g_keyboardLL == nullptr)
#endif #endif
@ -630,7 +642,7 @@ EHookResult MSWindowsHook::install()
uninstall(); uninstall();
return kHOOK_FAILED; return kHOOK_FAILED;
} }
// clang-format on
if (g_keyboardLL != nullptr || g_mouseLL != nullptr) { if (g_keyboardLL != nullptr || g_mouseLL != nullptr) {
g_hookThread = GetCurrentThreadId(); g_hookThread = GetCurrentThreadId();
return kHOOK_OKAY_LL; return kHOOK_OKAY_LL;

View file

@ -897,8 +897,7 @@ bool MSWindowsScreen::onPreDispatchPrimary(HWND, UINT message, WPARAM wParam, LP
return onMouseMove(static_cast<int32_t>(wParam), static_cast<int32_t>(lParam)); return onMouseMove(static_cast<int32_t>(wParam), static_cast<int32_t>(lParam));
case DESKFLOW_MSG_MOUSE_WHEEL: case DESKFLOW_MSG_MOUSE_WHEEL:
// XXX -- support x-axis scrolling return onMouseWheel(static_cast<int32_t>(lParam), static_cast<int32_t>(wParam));
return onMouseWheel(0, static_cast<int32_t>(wParam));
case DESKFLOW_MSG_PRE_WARP: { case DESKFLOW_MSG_PRE_WARP: {
// save position to compute delta of next motion // save position to compute delta of next motion

View file

@ -400,6 +400,7 @@ void OSXScreen::constructMouseButtonEventMap()
{kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown}, {kCGEventRightMouseUp, kCGEventRightMouseDragged, kCGEventRightMouseDown},
{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, {kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown},
{kCGEventOtherMouseUp, kCGEventOtherMouseDragged, kCGEventOtherMouseDown}, {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) { if (delta.x != 0 || delta.y != 0) {
// use server's acceleration with a little boost since other platforms // use server's acceleration with a little boost since other platforms
// take one wheel step as a larger step than the mac does. // take one wheel step as a larger step than the mac does.
delta = delta = applyScrollModifier(
applyScrollModifier({static_cast<int32_t>(3.0 * delta.x / 120.0), static_cast<int32_t>(3.0 * delta.y / 120.0)}); {static_cast<int32_t>(3.0 * delta.x / s_scrollDelta), static_cast<int32_t>(3.0 * delta.y / s_scrollDelta)}
);
// create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine // create a scroll event, post it and release it. not sure if kCGScrollEventUnitLine
// is the right choice here over kCGScrollEventUnitPixel // is the right choice here over kCGScrollEventUnitPixel
CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, delta.y, delta.x); CGEventRef scrollEvent = CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, delta.y, delta.x);

View file

@ -786,44 +786,39 @@ void XWindowsScreen::fakeMouseRelativeMove(int32_t dx, int32_t dy) const
void XWindowsScreen::fakeMouseWheel(ScrollDelta delta) const void XWindowsScreen::fakeMouseWheel(ScrollDelta delta) const
{ {
// XXX -- support x-axis scrolling if (delta.y == 0 && delta.x == 0) {
if (delta.y == 0) {
return; return;
} }
delta = applyScrollModifier(delta); delta = applyScrollModifier(delta);
bool isYScroll = delta.y != 0;
int32_t axisDelta = delta.y == 0 ? delta.x : delta.y;
// choose button depending on rotation direction ButtonID btn = kButtonNone;
const unsigned int xButton = mapButtonToX(delta.y >= 0 ? kX11ScrollWheelUp : kX11ScrollWheelDown); if (isYScroll) {
if (xButton == 0) { btn = axisDelta >= 0 ? kX11ScrollWheelUp : kX11ScrollWheelDown;
// If we get here, then the XServer does not support the scroll } else {
// wheel buttons, so send PageUp/PageDown keystrokes instead. btn = axisDelta >= 0 ? kX11ScrollWheelLeft : kX11ScrollWheelRight;
// Patch by Tom Chadwick. }
KeyCode keycode = 0; const unsigned int button = mapButtonToX(btn);
if (delta.y >= 0) {
keycode = XKeysymToKeycode(m_display, XK_Page_Up); // If the XServer does not support the Y scroll send PageUp/PageDown keystrokes instead.
} else { if (const KeyCode keycode = XKeysymToKeycode(m_display, delta.y >= 0 ? XK_Page_Up : XK_Page_Down);
keycode = XKeysymToKeycode(m_display, XK_Page_Down); button == 0 && isYScroll && keycode != 0) {
} XTestFakeKeyEvent(m_display, keycode, True, CurrentTime);
if (keycode != 0) { XTestFakeKeyEvent(m_display, keycode, False, CurrentTime);
XTestFakeKeyEvent(m_display, keycode, True, CurrentTime);
XTestFakeKeyEvent(m_display, keycode, False, CurrentTime);
}
return; return;
} }
// now use absolute value of delta // now use absolute value of delta
if (delta.y < 0) { if (axisDelta < 0) {
delta.y = -delta.y; axisDelta = -axisDelta;
} }
// Delta for a "click"
static const auto s_mouseDelta = 120;
// send as many clicks as necessary // send as many clicks as necessary
for (; delta.y >= 0; delta.y -= s_mouseDelta) { while (axisDelta >= 0) {
XTestFakeButtonEvent(m_display, xButton, True, CurrentTime); XTestFakeButtonEvent(m_display, button, True, CurrentTime);
XTestFakeButtonEvent(m_display, xButton, False, CurrentTime); XTestFakeButtonEvent(m_display, button, False, CurrentTime);
axisDelta -= s_scrollDelta;
} }
XFlush(m_display); XFlush(m_display);
} }
@ -1461,12 +1456,17 @@ void XWindowsScreen::onMouseRelease(const XButtonEvent &xbutton)
sendEvent(PrimaryScreenButtonUp, ButtonInfo::alloc(button, mask)); sendEvent(PrimaryScreenButtonUp, ButtonInfo::alloc(button, mask));
} else if (xbutton.button == 4) { } else if (xbutton.button == 4) {
// wheel forward (away from user) // wheel forward (away from user)
sendEvent(PrimaryScreenWheel, WheelInfo::alloc(0, 120)); sendEvent(PrimaryScreenWheel, WheelInfo::alloc(0, s_scrollDelta));
} else if (xbutton.button == 5) { } else if (xbutton.button == 5) {
// wheel backward (toward user) // 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) void XWindowsScreen::onMouseMove(const XMotionEvent &xmotion)
@ -1740,6 +1740,8 @@ ButtonID XWindowsScreen::mapButtonFromX(const XButtonEvent *event) const
case 1: case 1:
case 2: case 2:
case 3: case 3:
case 6:
case 7:
return static_cast<ButtonID>(button); // Handle Left, Middle and Right buttons return static_cast<ButtonID>(button); // Handle Left, Middle and Right buttons
case 8: case 8:
return kButtonExtra0; // Mouse 4 return kButtonExtra0; // Mouse 4
@ -1761,6 +1763,10 @@ unsigned int XWindowsScreen::mapButtonToX(ButtonID id) const
return 4; return 4;
case kX11ScrollWheelDown: case kX11ScrollWheelDown:
return 5; return 5;
case kX11ScrollWheelLeft:
return 6;
case kX11ScrollWheelRight:
return 7;
case kButtonExtra0: case kButtonExtra0:
return 8; return 8;
case kButtonExtra1: case kButtonExtra1: