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

View file

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

View file

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

View file

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

View file

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

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));
case DESKFLOW_MSG_MOUSE_WHEEL:
// XXX -- support x-axis scrolling
return onMouseWheel(0, static_cast<int32_t>(wParam));
return onMouseWheel(static_cast<int32_t>(lParam), static_cast<int32_t>(wParam));
case DESKFLOW_MSG_PRE_WARP: {
// save position to compute delta of next motion

View file

@ -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<int32_t>(3.0 * delta.x / 120.0), static_cast<int32_t>(3.0 * delta.y / 120.0)});
delta = applyScrollModifier(
{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
// is the right choice here over kCGScrollEventUnitPixel
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
{
// 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);
ButtonID btn = kButtonNone;
if (isYScroll) {
btn = axisDelta >= 0 ? kX11ScrollWheelUp : kX11ScrollWheelDown;
} else {
keycode = XKeysymToKeycode(m_display, XK_Page_Down);
btn = axisDelta >= 0 ? kX11ScrollWheelLeft : kX11ScrollWheelRight;
}
if (keycode != 0) {
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<ButtonID>(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: