From dd3944cabc32701a3f2624b2f26fd52ddb4837d8 Mon Sep 17 00:00:00 2001 From: rNoz Date: Tue, 30 Jun 2026 20:45:56 +0200 Subject: [PATCH] feat(macos): preserve the left/right side of modifier keys on the client The macOS client mapped every right-hand modifier (Control_R, Shift_R, Alt_R, Super/Meta_R) to its left-hand virtual key and always emitted the left device-dependent event flags, so a remote right-side modifier arrived on the client as the left one. Apps that distinguish the two (input methods that need Right Shift, hotkeys bound to Right Control, etc.) never saw the right key. Map the right-hand KeyIDs to their macOS right virtual keys, teach isModifier() and setKeyboardModifiers() about them, track which side is held, and emit the matching NX_DEVICER*KEYMASK device flag. This change is client-only; the macOS server key map is unchanged. fixes: #8486 Signed-off-by: rNoz --- src/lib/platform/OSXKeyState.cpp | 57 +++++++++++++++++++++++++------- src/lib/platform/OSXKeyState.h | 6 ++++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/src/lib/platform/OSXKeyState.cpp b/src/lib/platform/OSXKeyState.cpp index 5cc7b1277..796e95f9a 100644 --- a/src/lib/platform/OSXKeyState.cpp +++ b/src/lib/platform/OSXKeyState.cpp @@ -26,6 +26,10 @@ static const uint32_t s_controlVK = kVK_Control; static const uint32_t s_altVK = kVK_Option; static const uint32_t s_superVK = kVK_Command; static const uint32_t s_capsLockVK = kVK_CapsLock; +static const uint32_t s_shiftRightVK = kVK_RightShift; +static const uint32_t s_controlRightVK = kVK_RightControl; +static const uint32_t s_altRightVK = kVK_RightOption; +static const uint32_t s_superRightVK = kVK_RightCommand; static const uint32_t s_numLockVK = kVK_ANSI_KeypadClear; // 71 static const uint32_t s_brightnessUp = 144; @@ -95,18 +99,18 @@ static const KeyEntry s_controlKeys[] = { // to map to. also the enter key with numlock on is a modifier but i // don't know which. - // modifier keys. OS X doesn't seem to support right handed versions - // of modifier keys so we map them to the left handed versions. + // modifier keys. map the left and right variants to their respective + // macOS virtual keys so the modifier side is preserved on the client. {kKeyShift_L, s_shiftVK}, - {kKeyShift_R, s_shiftVK}, // 60 + {kKeyShift_R, s_shiftRightVK}, {kKeyControl_L, s_controlVK}, - {kKeyControl_R, s_controlVK}, // 62 + {kKeyControl_R, s_controlRightVK}, {kKeyAlt_L, s_altVK}, - {kKeyAlt_R, s_altVK}, + {kKeyAlt_R, s_altRightVK}, {kKeySuper_L, s_superVK}, - {kKeySuper_R, s_superVK}, // 61 + {kKeySuper_R, s_superRightVK}, {kKeyMeta_L, s_superVK}, - {kKeyMeta_R, s_superVK}, // 61 + {kKeyMeta_R, s_superRightVK}, // toggle modifiers {kKeyNumLock, s_numLockVK}, @@ -168,7 +172,8 @@ io_connect_t getEventDriver() bool isModifier(uint8_t virtualKey) { - static std::set modifiers{s_shiftVK, s_superVK, s_altVK, s_controlVK, s_capsLockVK}; + static std::set modifiers{s_shiftVK, s_controlVK, s_altVK, s_superVK, s_capsLockVK, + s_shiftRightVK, s_controlRightVK, s_altRightVK, s_superRightVK}; return (modifiers.find(virtualKey) != modifiers.end()); } @@ -228,6 +233,10 @@ void OSXKeyState::init() m_altPressed = false; m_superPressed = false; m_capsPressed = false; + m_shiftRightPressed = false; + m_controlRightPressed = false; + m_altRightPressed = false; + m_superRightPressed = false; // build virtual key map for (size_t i = 0; i < sizeof(s_controlKeys) / sizeof(s_controlKeys[0]); ++i) { @@ -549,19 +558,19 @@ CGEventFlags OSXKeyState::getDeviceDependedFlags() const CGEventFlags modifiers = 0; if (m_shiftPressed) { - modifiers |= NX_DEVICELSHIFTKEYMASK; + modifiers |= m_shiftRightPressed ? NX_DEVICERSHIFTKEYMASK : NX_DEVICELSHIFTKEYMASK; } if (m_controlPressed) { - modifiers |= NX_DEVICELCTLKEYMASK; + modifiers |= m_controlRightPressed ? NX_DEVICERCTLKEYMASK : NX_DEVICELCTLKEYMASK; } if (m_altPressed) { - modifiers |= NX_DEVICELALTKEYMASK; + modifiers |= m_altRightPressed ? NX_DEVICERALTKEYMASK : NX_DEVICELALTKEYMASK; } if (m_superPressed) { - modifiers |= NX_DEVICELCMDKEYMASK; + modifiers |= m_superRightPressed ? NX_DEVICERCMDKEYMASK : NX_DEVICELCMDKEYMASK; } return modifiers; @@ -585,15 +594,39 @@ void OSXKeyState::setKeyboardModifiers(CGKeyCode virtualKey, bool keyDown) switch (virtualKey) { case s_shiftVK: m_shiftPressed = keyDown; + if (keyDown) + m_shiftRightPressed = false; + break; + case s_shiftRightVK: + m_shiftPressed = keyDown; + m_shiftRightPressed = keyDown; break; case s_controlVK: m_controlPressed = keyDown; + if (keyDown) + m_controlRightPressed = false; + break; + case s_controlRightVK: + m_controlPressed = keyDown; + m_controlRightPressed = keyDown; break; case s_altVK: m_altPressed = keyDown; + if (keyDown) + m_altRightPressed = false; + break; + case s_altRightVK: + m_altPressed = keyDown; + m_altRightPressed = keyDown; break; case s_superVK: m_superPressed = keyDown; + if (keyDown) + m_superRightPressed = false; + break; + case s_superRightVK: + m_superPressed = keyDown; + m_superRightPressed = keyDown; break; case s_capsLockVK: m_capsPressed = keyDown; diff --git a/src/lib/platform/OSXKeyState.h b/src/lib/platform/OSXKeyState.h index 46d363a31..255dfe69d 100644 --- a/src/lib/platform/OSXKeyState.h +++ b/src/lib/platform/OSXKeyState.h @@ -162,4 +162,10 @@ private: bool m_altPressed; bool m_superPressed; bool m_capsPressed; + // track whether the right-hand variant of a modifier is held so the + // device-dependent event flags report the correct side. + bool m_shiftRightPressed; + bool m_controlRightPressed; + bool m_altRightPressed; + bool m_superRightPressed; };