diff --git a/ChangeLog b/ChangeLog index 3468361c8..1877cd0eb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,7 @@ Enhancements: - #7035 Add Fedora 34 build - #7037 Add Ubuntu 21.04 build - #7030 | #7041 | #7043 Add user notification for secure input on Mac +- #7044 Apply natural scroll setting independently on each client - #7040 Support "Kana" and "Eisu" keys on Japanese Apple Pro Keyboard (JIS) =========== diff --git a/src/lib/arch/unix/ArchSystemUnix.cpp b/src/lib/arch/unix/ArchSystemUnix.cpp index e877f23eb..a25e95af1 100644 --- a/src/lib/arch/unix/ArchSystemUnix.cpp +++ b/src/lib/arch/unix/ArchSystemUnix.cpp @@ -19,6 +19,10 @@ #include "arch/unix/ArchSystemUnix.h" #include +#include +#include +#include +#include // // ArchSystemUnix @@ -78,3 +82,21 @@ ArchSystemUnix::getLibsUsed(void) const { return "not implemented.\nuse lsof on shell"; } + +std::string +ArchSystemUnix::runCommand(const std::string& cmd) +{ + std::array buffer; + std::string result; + std::unique_ptr pipe(popen(cmd.c_str(), "r"), &pclose); + if (!pipe) + { + return ""; + } + + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) + { + result += buffer.data(); + } + return result; +} diff --git a/src/lib/arch/unix/ArchSystemUnix.h b/src/lib/arch/unix/ArchSystemUnix.h index f7c64218a..a8e9fa002 100644 --- a/src/lib/arch/unix/ArchSystemUnix.h +++ b/src/lib/arch/unix/ArchSystemUnix.h @@ -35,4 +35,5 @@ public: virtual void setting(const std::string&, const std::string&) const; virtual std::string getLibsUsed(void) const; + static std::string runCommand(const std::string& cmd); }; diff --git a/src/lib/platform/MSWindowsScreen.cpp b/src/lib/platform/MSWindowsScreen.cpp index 73d1ecd9a..6034e20eb 100644 --- a/src/lib/platform/MSWindowsScreen.cpp +++ b/src/lib/platform/MSWindowsScreen.cpp @@ -46,6 +46,7 @@ #include #include #include +#include // suppress warning about GetVersionEx, which is used indirectly in this compilation unit. #pragma warning(disable: 4996) @@ -344,6 +345,11 @@ MSWindowsScreen::leave() // tell desk that we're leaving and tell it the keyboard layout m_desks->leave(m_keyLayout); + // Forcefully update scrolling direction + // Will keep server updated when moving cursor + allowScrollDirectionUpdate(); + updateScrollDirection(); + if (m_isPrimary) { // warp to center @@ -829,6 +835,8 @@ MSWindowsScreen::fakeMouseRelativeMove(SInt32 dx, SInt32 dy) const void MSWindowsScreen::fakeMouseWheel(SInt32 xDelta, SInt32 yDelta) const { + xDelta = mapScrollFromSynergy(xDelta); + yDelta = mapScrollFromSynergy(yDelta); m_desks->fakeMouseWheel(xDelta, yDelta); } @@ -1473,6 +1481,8 @@ MSWindowsScreen::onMouseWheel(SInt32 xDelta, SInt32 yDelta) // ignore message if posted prior to last mark change if (!ignore()) { LOG((CLOG_DEBUG1 "event: button wheel delta=%+d,%+d", xDelta, yDelta)); + xDelta = mapScrollToSynergy(xDelta); + yDelta = mapScrollToSynergy(yDelta); sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(xDelta, yDelta)); } return true; @@ -2022,3 +2032,56 @@ MSWindowsScreen::isModifierRepeat(KeyModifierMask oldState, KeyModifierMask stat return result; } + +SInt32 +MSWindowsScreen::mapScrollToSynergy(SInt32 delta) const +{ + // for mouse wheel the delta will be a multiple of WHEEL_DELTA + if (delta % WHEEL_DELTA == 0) + { + return delta * m_scrollDirectionMouse; + } + else + { + return delta * m_scrollDirectionTouchpad; + } +} + +SInt32 +MSWindowsScreen::mapScrollFromSynergy(SInt32 delta) const +{ + // use mouse scrolling direction to invert + if (m_scrollDirectionMouse < 0) + return -delta; + return delta; +} + +void +MSWindowsScreen::updateScrollDirection() +{ + static const TCHAR* const touchpadScrollDirectionNames[] = { + _T("SOFTWARE"), + _T("Microsoft"), + _T("Windows"), + _T("CurrentVersion"), + _T("PrecisionTouchPad"), + NULL + }; + + if (m_shouldUpdateScrollDirection) + { + m_shouldUpdateScrollDirection = false; + + std::thread scrollDirectionUpdateThread([&] { + HKEY key = ArchMiscWindows::openKey(HKEY_CURRENT_USER, touchpadScrollDirectionNames); + if (key) + { + DWORD scroll = ArchMiscWindows::readValueInt(key, _T("ScrollDirection")); + ArchMiscWindows::closeKey(key); + if (scroll == 0) m_scrollDirectionTouchpad = 1; + else m_scrollDirectionTouchpad = -1; + } + }); + scrollDirectionUpdateThread.detach(); + } +} \ No newline at end of file diff --git a/src/lib/platform/MSWindowsScreen.h b/src/lib/platform/MSWindowsScreen.h index b8a6e2536..d72889cfa 100644 --- a/src/lib/platform/MSWindowsScreen.h +++ b/src/lib/platform/MSWindowsScreen.h @@ -140,6 +140,7 @@ public: virtual const String& getDropTarget() const; String getSecureInputApp() const override; + void updateScrollDirection() override; protected: // IPlatformScreen overrides @@ -247,6 +248,9 @@ private: // HACK // send drag info and data back to server void sendDragThread(void*); + SInt32 mapScrollToSynergy(SInt32 delta) const; + SInt32 mapScrollFromSynergy(SInt32 delta) const; + private: struct HotKeyItem { public: @@ -366,4 +370,8 @@ private: Thread* m_sendDragThread; PrimaryKeyDownList m_primaryKeyDownList; + + // -1 for natural scrolling direction, 1 otherwise + SInt32 m_scrollDirectionMouse = 1; + SInt32 m_scrollDirectionTouchpad = 1; }; diff --git a/src/lib/platform/OSXScreen.h b/src/lib/platform/OSXScreen.h index 397f61c0b..96dd928a4 100644 --- a/src/lib/platform/OSXScreen.h +++ b/src/lib/platform/OSXScreen.h @@ -341,6 +341,7 @@ private: Thread* m_getDropTargetThread; String m_dropTarget; + void updateScrollDirection() override; // -1 for natural scrolling direction, 1 otherwise SInt32 m_scrollDirection = 1; diff --git a/src/lib/platform/OSXScreen.mm b/src/lib/platform/OSXScreen.mm index a81e54cf1..df29e6d8e 100644 --- a/src/lib/platform/OSXScreen.mm +++ b/src/lib/platform/OSXScreen.mm @@ -859,8 +859,6 @@ OSXScreen::enter() setZeroSuppressionInterval(); } else { - m_scrollDirection = [[[NSUserDefaults standardUserDefaults] objectForKey:@"com.apple.swipescrolldirection"] boolValue] ? -1 : 1; - // reset buttons m_buttonState.reset(); @@ -1465,7 +1463,7 @@ OSXScreen::mapScrollWheelToSynergy(SInt32 x) const // return accelerated scrolling but not exponentially scaled as it is // on the mac. double d = (1.0 + getScrollSpeed()) * x / getScrollSpeedFactor(); - return static_cast(120.0 * d); + return static_cast(m_scrollDirection * 120.0 * d); } SInt32 @@ -2242,6 +2240,17 @@ getProcessName(int pid) return buf; } +void +OSXScreen::updateScrollDirection() +{ + if(m_shouldUpdateScrollDirection) + { + LOG((CLOG_DEBUG "updated scrolling direction")); + m_scrollDirection = [[[NSUserDefaults standardUserDefaults] objectForKey:@"com.apple.swipescrolldirection"] boolValue] ? -1 : 1; + m_shouldUpdateScrollDirection = false; + } +} + #pragma GCC diagnostic ignored "-Wdeprecated-declarations" void diff --git a/src/lib/platform/XWindowsScreen.cpp b/src/lib/platform/XWindowsScreen.cpp index 560d3c7ac..5a9633d1a 100644 --- a/src/lib/platform/XWindowsScreen.cpp +++ b/src/lib/platform/XWindowsScreen.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #if X_DISPLAY_MISSING # error X11 is required to build synergy #else @@ -867,6 +868,12 @@ XWindowsScreen::fakeMouseWheel(SInt32, SInt32 yDelta) const return; } + // use mouse scroll direction for inversion + if( m_scrollDirectionMouse < 0 ) + { + yDelta = -yDelta; + } + // choose button depending on rotation direction const unsigned int xButton = mapButtonToX(static_cast( (yDelta >= 0) ? -1 : -2)); @@ -1624,11 +1631,13 @@ XWindowsScreen::onMouseRelease(const XButtonEvent& xbutton) } else if (xbutton.button == 4) { // wheel forward (away from user) - sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, 120)); + // invert for natural scroll setting + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, 120 * m_scrollDirectionMouse)); } else if (xbutton.button == 5) { // wheel backward (toward user) - sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, -120)); + // invert for natural scroll setting + sendEvent(m_events->forIPrimaryScreen().wheel(), WheelInfo::alloc(0, -120 * m_scrollDirectionMouse)); } // XXX -- support x-axis scrolling } @@ -2153,3 +2162,27 @@ XWindowsScreen::selectXIRawMotion() free(mask.mask); } #endif + +void +XWindowsScreen::updateScrollDirection() +{ + if (m_shouldUpdateScrollDirection) + { + m_shouldUpdateScrollDirection = false; + + std::thread scrollDirectionUpdateThread([this]{ + std::string mouseScroll = ArchSystemUnix::runCommand("gsettings get org.gnome.desktop.peripherals.mouse natural-scroll"); + if(mouseScroll == "false\n") + m_scrollDirectionMouse = 1; + else if(mouseScroll == "true\n") + m_scrollDirectionMouse = -1; + + std::string touchpadScroll = ArchSystemUnix::runCommand("gsettings get org.gnome.desktop.peripherals.touchpad natural-scroll"); + if(touchpadScroll == "false\n") + m_scrollDirectionTouchpad = 1; + else if(touchpadScroll == "true\n") + m_scrollDirectionTouchpad = -1; + }); + scrollDirectionUpdateThread.detach(); + } +} diff --git a/src/lib/platform/XWindowsScreen.h b/src/lib/platform/XWindowsScreen.h index 19689a2fc..facafa0f5 100644 --- a/src/lib/platform/XWindowsScreen.h +++ b/src/lib/platform/XWindowsScreen.h @@ -86,6 +86,8 @@ public: virtual bool isPrimary() const; String getSecureInputApp() const override; + void updateScrollDirection() override; + protected: // IPlatformScreen overrides virtual void handleSystemEvent(const Event&, void*); @@ -251,4 +253,8 @@ private: // pointer to (singleton) screen. this is only needed by // ioErrorHandler(). static XWindowsScreen* s_screen; + + // -1 for natural scrolling direction, 1 otherwise + SInt32 m_scrollDirectionMouse = 1; + SInt32 m_scrollDirectionTouchpad = 1; }; diff --git a/src/lib/synergy/IPlatformScreen.h b/src/lib/synergy/IPlatformScreen.h index 6a24019fa..bde73f735 100644 --- a/src/lib/synergy/IPlatformScreen.h +++ b/src/lib/synergy/IPlatformScreen.h @@ -139,6 +139,20 @@ public: */ virtual String getSecureInputApp() const = 0; + //! Allow updating wheel scrolling direction + /*! + Sets a flag to allow updating the scrolling direction automatically + when scrolling + */ + virtual void allowScrollDirectionUpdate() = 0; + + //! Update the scrolling direction if allowed + /*! + If relevant flag is set should read a system settings to apply a + correct scrolling direction + */ + virtual void updateScrollDirection() = 0; + //@} //! @name accessors //@{ diff --git a/src/lib/synergy/PlatformScreen.h b/src/lib/synergy/PlatformScreen.h index 16dc904ad..609131aa5 100644 --- a/src/lib/synergy/PlatformScreen.h +++ b/src/lib/synergy/PlatformScreen.h @@ -102,6 +102,9 @@ public: virtual const String& getDropTarget() const { throw std::runtime_error("getDropTarget not implemented"); } + void allowScrollDirectionUpdate() override { m_shouldUpdateScrollDirection = true; } + void updateScrollDirection() override = 0; + protected: //! Update mouse buttons /*! @@ -124,4 +127,5 @@ protected: String m_draggingFilename; bool m_draggingStarted; bool m_fakeDraggingStarted; + bool m_shouldUpdateScrollDirection = false; }; diff --git a/src/lib/synergy/Screen.cpp b/src/lib/synergy/Screen.cpp index 18245b840..89b943851 100644 --- a/src/lib/synergy/Screen.cpp +++ b/src/lib/synergy/Screen.cpp @@ -114,6 +114,11 @@ Screen::enter(KeyModifierMask toggleMask) // now on screen m_entered = true; + // Forcefully update scrolling direction + // Will keep clients updated when moving cursor + m_screen->allowScrollDirectionUpdate(); + m_screen->updateScrollDirection(); + m_screen->enter(); if (m_isPrimary) { enterPrimary(); @@ -129,6 +134,11 @@ Screen::leave() assert(m_entered == true); LOG((CLOG_INFO "leaving screen")); + // Forcefully update scrolling direction + // Will keep server updated when moving cursor + m_screen->allowScrollDirectionUpdate(); + m_screen->updateScrollDirection(); + if (!m_screen->leave()) { return false; } @@ -212,6 +222,11 @@ Screen::keyUp(KeyID, KeyModifierMask, KeyButton button) void Screen::mouseDown(ButtonID button) { + // No other convinient way to check if scroll direction was changed + // If mouse button is pressed is good enough indication to allow checking + // for scroll direction + m_screen->allowScrollDirectionUpdate(); + m_screen->fakeMouseButton(button, true); } @@ -239,6 +254,9 @@ void Screen::mouseWheel(SInt32 xDelta, SInt32 yDelta) { assert(!m_isPrimary); + // update scroll direction if necessary + m_screen->updateScrollDirection(); + m_screen->fakeMouseWheel(xDelta, yDelta); }