From b1d956af2cae5633ebd564201f271a8cf2d7284e Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 6 May 2026 17:52:31 +0800 Subject: [PATCH] [fix] fix left/right modifier key injection while preserving scan code metadata --- src/device_controller/device_controller.h | 29 +++-- .../keyboard/linux/keyboard_capturer.cpp | 9 +- .../keyboard/linux/keyboard_capturer.h | 7 +- .../linux/keyboard_capturer_wayland.cpp | 8 +- .../keyboard/mac/keyboard_capturer.cpp | 22 ++-- .../keyboard/mac/keyboard_capturer.h | 4 +- .../keyboard/windows/keyboard_capturer.cpp | 78 ++++++++++-- .../keyboard/windows/keyboard_capturer.h | 4 +- src/gui/render.cpp | 5 +- src/gui/render.h | 3 +- src/gui/render_callback.cpp | 57 ++++++++- src/service/windows/service_host.cpp | 85 +++++++++++--- src/service/windows/service_host.h | 7 +- src/service/windows/session_helper_main.cpp | 111 +++++++++++++++--- 14 files changed, 354 insertions(+), 75 deletions(-) diff --git a/src/device_controller/device_controller.h b/src/device_controller/device_controller.h index fcfcbe6..f1f7fd3 100644 --- a/src/device_controller/device_controller.h +++ b/src/device_controller/device_controller.h @@ -7,9 +7,10 @@ #ifndef _DEVICE_CONTROLLER_H_ #define _DEVICE_CONTROLLER_H_ -#include #include +#include +#include #include #include @@ -49,6 +50,8 @@ typedef struct { typedef struct { size_t key_value; + uint32_t scan_code; + bool extended; KeyFlag flag; } Key; @@ -103,7 +106,10 @@ struct RemoteAction { {"x", a.m.x}, {"y", a.m.y}, {"s", a.m.s}, {"flag", a.m.flag}}; break; case ControlType::keyboard: - j["keyboard"] = {{"key_value", a.k.key_value}, {"flag", a.k.flag}}; + j["keyboard"] = {{"key_value", a.k.key_value}, + {"scan_code", a.k.scan_code}, + {"extended", a.k.extended}, + {"flag", a.k.flag}}; break; case ControlType::audio_capture: j["audio_capture"] = a.a; @@ -113,8 +119,7 @@ struct RemoteAction { break; case ControlType::service_status: j["service_status"] = {{"available", a.ss.available}, - {"interactive_stage", - a.ss.interactive_stage}}; + {"interactive_stage", a.ss.interactive_stage}}; break; case ControlType::service_command: j["service_command"] = {{"flag", a.c.flag}}; @@ -152,6 +157,9 @@ struct RemoteAction { break; case ControlType::keyboard: out.k.key_value = j.at("keyboard").at("key_value").get(); + out.k.scan_code = + j.at("keyboard").value("scan_code", static_cast(0)); + out.k.extended = j.at("keyboard").value("extended", false); out.k.flag = (KeyFlag)j.at("keyboard").at("flag").get(); break; case ControlType::audio_capture: @@ -164,16 +172,15 @@ struct RemoteAction { const auto& service_status_json = j.at("service_status"); out.ss.available = service_status_json.value("available", false); std::string interactive_stage = - service_status_json.value("interactive_stage", std::string()); + service_status_json.value("interactive_stage", std::string()); std::strncpy(out.ss.interactive_stage, interactive_stage.c_str(), - sizeof(out.ss.interactive_stage) - 1); - out.ss.interactive_stage[sizeof(out.ss.interactive_stage) - 1] = - '\0'; + sizeof(out.ss.interactive_stage) - 1); + out.ss.interactive_stage[sizeof(out.ss.interactive_stage) - 1] = '\0'; break; } case ControlType::service_command: out.c.flag = static_cast( - j.at("service_command").at("flag").get()); + j.at("service_command").at("flag").get()); break; case ControlType::host_infomation: { std::string host_name = @@ -212,8 +219,8 @@ struct RemoteAction { } }; -// int key_code, bool is_down -typedef void (*OnKeyAction)(int, bool, void*); +// int key_code, bool is_down, uint32_t scan_code, bool extended +typedef void (*OnKeyAction)(int, bool, uint32_t, bool, void*); class DeviceController { public: diff --git a/src/device_controller/keyboard/linux/keyboard_capturer.cpp b/src/device_controller/keyboard/linux/keyboard_capturer.cpp index beceda4..9da49e1 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/linux/keyboard_capturer.cpp @@ -37,7 +37,7 @@ static int KeyboardEventHandler(Display* display, XEvent* event) { bool is_key_down = (event->xkey.type == KeyPress); if (g_on_key_action) { - g_on_key_action(key_code, is_key_down, g_user_ptr); + g_on_key_action(key_code, is_key_down, 0, false, g_user_ptr); } } return 0; @@ -146,7 +146,10 @@ int KeyboardCapturer::Unhook() { return 0; } -int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { +int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code, bool extended) { + (void)scan_code; + (void)extended; if (IsWaylandSession()) { if (!use_wayland_portal_ && !wayland_init_attempted_) { wayland_init_attempted_ = true; @@ -159,7 +162,7 @@ int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { } if (use_wayland_portal_) { - return SendWaylandKeyboardCommand(key_code, is_down); + return SendWaylandKeyboardCommand(key_code, is_down, scan_code, extended); } } diff --git a/src/device_controller/keyboard/linux/keyboard_capturer.h b/src/device_controller/keyboard/linux/keyboard_capturer.h index e440744..2ef2048 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer.h +++ b/src/device_controller/keyboard/linux/keyboard_capturer.h @@ -32,12 +32,15 @@ class KeyboardCapturer : public DeviceController { public: virtual int Hook(OnKeyAction on_key_action, void* user_ptr); virtual int Unhook(); - virtual int SendKeyboardCommand(int key_code, bool is_down); + virtual int SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false); private: bool InitWaylandPortal(); void CleanupWaylandPortal(); - int SendWaylandKeyboardCommand(int key_code, bool is_down); + int SendWaylandKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, + bool extended); bool NotifyWaylandKeyboardKeysym(int keysym, uint32_t state); bool NotifyWaylandKeyboardKeycode(int keycode, uint32_t state); bool SendWaylandPortalVoidCall(const char* method_name, diff --git a/src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp b/src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp index bfc34df..318b64e 100644 --- a/src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp +++ b/src/device_controller/keyboard/linux/keyboard_capturer_wayland.cpp @@ -575,8 +575,12 @@ void KeyboardCapturer::CleanupWaylandPortal() { wayland_session_handle_.clear(); } -int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down) { +int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code, + bool extended) { #if defined(CROSSDESK_HAS_WAYLAND_CAPTURER) && CROSSDESK_HAS_WAYLAND_CAPTURER + (void)scan_code; + (void)extended; if (!dbus_connection_ || wayland_session_handle_.empty()) { return -1; } @@ -613,6 +617,8 @@ int KeyboardCapturer::SendWaylandKeyboardCommand(int key_code, bool is_down) { #else (void)key_code; (void)is_down; + (void)scan_code; + (void)extended; return -1; #endif } diff --git a/src/device_controller/keyboard/mac/keyboard_capturer.cpp b/src/device_controller/keyboard/mac/keyboard_capturer.cpp index 99df50a..3f4ddf1 100644 --- a/src/device_controller/keyboard/mac/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/mac/keyboard_capturer.cpp @@ -119,7 +119,7 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode)); int vk_code = ResolveVkCodeFromMacEvent(event, key_code, is_key_down); if (vk_code >= 0) { - g_on_key_action(vk_code, is_key_down, g_user_ptr); + g_on_key_action(vk_code, is_key_down, 0, false, g_user_ptr); } } else if (type == kCGEventFlagsChanged) { CGEventFlags current_flags = CGEventGetFlags(event); @@ -135,35 +135,40 @@ CGEventRef eventCallback(CGEventTapProxy proxy, CGEventType type, bool caps_lock_state = (current_flags & kCGEventFlagMaskAlphaShift) != 0; if (caps_lock_state != keyboard_capturer->caps_lock_flag_) { keyboard_capturer->caps_lock_flag_ = caps_lock_state; - g_on_key_action(vk_code, keyboard_capturer->caps_lock_flag_, g_user_ptr); + g_on_key_action(vk_code, keyboard_capturer->caps_lock_flag_, 0, false, + g_user_ptr); } // shift bool shift_state = (current_flags & kCGEventFlagMaskShift) != 0; if (shift_state != keyboard_capturer->shift_flag_) { keyboard_capturer->shift_flag_ = shift_state; - g_on_key_action(vk_code, keyboard_capturer->shift_flag_, g_user_ptr); + g_on_key_action(vk_code, keyboard_capturer->shift_flag_, 0, false, + g_user_ptr); } // control bool control_state = (current_flags & kCGEventFlagMaskControl) != 0; if (control_state != keyboard_capturer->control_flag_) { keyboard_capturer->control_flag_ = control_state; - g_on_key_action(vk_code, keyboard_capturer->control_flag_, g_user_ptr); + g_on_key_action(vk_code, keyboard_capturer->control_flag_, 0, false, + g_user_ptr); } // option bool option_state = (current_flags & kCGEventFlagMaskAlternate) != 0; if (option_state != keyboard_capturer->option_flag_) { keyboard_capturer->option_flag_ = option_state; - g_on_key_action(vk_code, keyboard_capturer->option_flag_, g_user_ptr); + g_on_key_action(vk_code, keyboard_capturer->option_flag_, 0, false, + g_user_ptr); } // command bool command_state = (current_flags & kCGEventFlagMaskCommand) != 0; if (command_state != keyboard_capturer->command_flag_) { keyboard_capturer->command_flag_ = command_state; - g_on_key_action(vk_code, keyboard_capturer->command_flag_, g_user_ptr); + g_on_key_action(vk_code, keyboard_capturer->command_flag_, 0, false, + g_user_ptr); } } @@ -264,7 +269,10 @@ inline bool IsFunctionKey(int key_code) { } } -int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { +int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code, bool extended) { + (void)scan_code; + (void)extended; if (vkCodeToCGKeyCode.find(key_code) != vkCodeToCGKeyCode.end()) { CGKeyCode cg_key_code = vkCodeToCGKeyCode[key_code]; CGEventRef event = CGEventCreateKeyboardEvent(NULL, cg_key_code, is_down); diff --git a/src/device_controller/keyboard/mac/keyboard_capturer.h b/src/device_controller/keyboard/mac/keyboard_capturer.h index 3220835..e623309 100644 --- a/src/device_controller/keyboard/mac/keyboard_capturer.h +++ b/src/device_controller/keyboard/mac/keyboard_capturer.h @@ -21,7 +21,9 @@ class KeyboardCapturer : public DeviceController { public: virtual int Hook(OnKeyAction on_key_action, void* user_ptr); virtual int Unhook(); - virtual int SendKeyboardCommand(int key_code, bool is_down); + virtual int SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false); private: CFMachPortRef event_tap_ = nullptr; diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.cpp b/src/device_controller/keyboard/windows/keyboard_capturer.cpp index f6338e0..7cd2b6e 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.cpp +++ b/src/device_controller/keyboard/windows/keyboard_capturer.cpp @@ -7,14 +7,56 @@ namespace crossdesk { static OnKeyAction g_on_key_action = nullptr; static void* g_user_ptr = nullptr; +static int NormalizeModifierVkCode(const KBDLLHOOKSTRUCT* kb_data) { + if (kb_data == nullptr) { + return -1; + } + + if (kb_data->vkCode != VK_SHIFT && kb_data->vkCode != VK_CONTROL && + kb_data->vkCode != VK_MENU) { + return static_cast(kb_data->vkCode); + } + + UINT scan_code = static_cast(kb_data->scanCode & 0xFF); + if ((kb_data->flags & LLKHF_EXTENDED) != 0) { + scan_code |= 0xE000; + } + + const UINT normalized_vk = MapVirtualKeyW(scan_code, MAPVK_VSC_TO_VK_EX); + if (normalized_vk != 0) { + return static_cast(normalized_vk); + } + + return static_cast(kb_data->vkCode); +} + +static bool PreferSideSpecificVkInjection(int key_code) { + switch (key_code) { + case VK_LSHIFT: + case VK_RSHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + return true; + default: + return false; + } +} + LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION && g_on_key_action) { KBDLLHOOKSTRUCT* kbData = reinterpret_cast(lParam); + const int key_code = NormalizeModifierVkCode(kbData); if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) { - g_on_key_action(kbData->vkCode, true, g_user_ptr); + g_on_key_action(key_code, true, kbData->scanCode, + (kbData->flags & LLKHF_EXTENDED) != 0, g_user_ptr); } else if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP) { - g_on_key_action(kbData->vkCode, false, g_user_ptr); + g_on_key_action(key_code, false, kbData->scanCode, + (kbData->flags & LLKHF_EXTENDED) != 0, g_user_ptr); } return 1; } @@ -49,20 +91,40 @@ int KeyboardCapturer::Unhook() { } // apply remote keyboard commands to the local machine -int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down) { +int KeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code, bool extended) { INPUT input = {0}; input.type = INPUT_KEYBOARD; - input.ki.wVk = (WORD)key_code; - const UINT scan_code = - MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); - if (scan_code != 0) { + const bool prefer_vk = PreferSideSpecificVkInjection(key_code); + const UINT resolved_scan_code = + scan_code != 0 + ? static_cast(scan_code & 0xFF) | (extended ? 0xE000u : 0u) + : MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); + + if (scan_code != 0 && !prefer_vk) { input.ki.wVk = 0; input.ki.wScan = static_cast(scan_code & 0xFF); input.ki.dwFlags |= KEYEVENTF_SCANCODE; - if ((scan_code & 0xFF00) != 0) { + if (extended) { input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } + } else { + input.ki.wVk = (WORD)key_code; + + if (prefer_vk && resolved_scan_code != 0) { + input.ki.wScan = static_cast(resolved_scan_code & 0xFF); + if ((resolved_scan_code & 0xFF00) != 0) { + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + } else if (resolved_scan_code != 0) { + input.ki.wVk = 0; + input.ki.wScan = static_cast(resolved_scan_code & 0xFF); + input.ki.dwFlags |= KEYEVENTF_SCANCODE; + if ((resolved_scan_code & 0xFF00) != 0) { + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + } } if (!is_down) { diff --git a/src/device_controller/keyboard/windows/keyboard_capturer.h b/src/device_controller/keyboard/windows/keyboard_capturer.h index 6a5d8fe..f1c4a7b 100644 --- a/src/device_controller/keyboard/windows/keyboard_capturer.h +++ b/src/device_controller/keyboard/windows/keyboard_capturer.h @@ -21,7 +21,9 @@ class KeyboardCapturer : public DeviceController { public: virtual int Hook(OnKeyAction on_key_action, void* user_ptr); virtual int Unhook(); - virtual int SendKeyboardCommand(int key_code, bool is_down); + virtual int SendKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false); private: HHOOK keyboard_hook_ = nullptr; diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 0a2d459..736f340 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -812,10 +812,11 @@ int Render::StartKeyboardCapturer() { } int keyboard_capturer_init_ret = keyboard_capturer_->Hook( - [](int key_code, bool is_down, void* user_ptr) { + [](int key_code, bool is_down, uint32_t scan_code, bool extended, + void* user_ptr) { if (user_ptr) { Render* render = (Render*)user_ptr; - render->SendKeyCommand(key_code, is_down); + render->SendKeyCommand(key_code, is_down, scan_code, extended); } }, this); diff --git a/src/gui/render.h b/src/gui/render.h index 17d5038..732351b 100644 --- a/src/gui/render.h +++ b/src/gui/render.h @@ -339,7 +339,8 @@ class Render { static void FreeRemoteAction(RemoteAction& action); private: - int SendKeyCommand(int key_code, bool is_down); + int SendKeyCommand(int key_code, bool is_down, uint32_t scan_code = 0, + bool extended = false); static bool IsModifierVkKey(int key_code); void TrackPressedKeyState(int key_code, bool is_down); void ForceReleasePressedKeys(); diff --git a/src/gui/render_callback.cpp b/src/gui/render_callback.cpp index a99ea6d..cec98cf 100644 --- a/src/gui/render_callback.cpp +++ b/src/gui/render_callback.cpp @@ -200,6 +200,40 @@ int TranslateSdlKeyboardEventToVk(const SDL_KeyboardEvent& event) { } } +#if _WIN32 +int NormalizeWindowsModifierVk(int key_code, uint32_t scan_code, + bool extended) { + if (key_code != 0x10 && key_code != 0x11 && key_code != 0x12) { + return key_code; + } + + UINT scan_code_with_prefix = static_cast(scan_code & 0xFF); + if (extended) { + scan_code_with_prefix |= 0xE000; + } + + const UINT normalized_vk = + MapVirtualKeyW(scan_code_with_prefix, MAPVK_VSC_TO_VK_EX); + return normalized_vk != 0 ? static_cast(normalized_vk) : key_code; +} + +void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t* scan_code_out, + bool* extended_out) { + if (scan_code_out == nullptr || extended_out == nullptr) { + return; + } + + const UINT scan_code = + MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); + if (scan_code == 0) { + return; + } + + *scan_code_out = static_cast(scan_code & 0xFF); + *extended_out = (scan_code & 0xFF00) != 0; +} +#endif + #if _WIN32 constexpr uint32_t kSecureDesktopInputLogIntervalMs = 2000; @@ -353,15 +387,26 @@ void Render::ForceReleasePressedKeys() { } } -int Render::SendKeyCommand(int key_code, bool is_down) { - RemoteAction remote_action; +int Render::SendKeyCommand(int key_code, bool is_down, uint32_t scan_code, + bool extended) { + RemoteAction remote_action{}; remote_action.type = ControlType::keyboard; if (is_down) { remote_action.k.flag = KeyFlag::key_down; } else { remote_action.k.flag = KeyFlag::key_up; } + +#if _WIN32 + if (scan_code == 0) { + PopulateWindowsKeyMetadataFromVk(key_code, &scan_code, &extended); + } + key_code = NormalizeWindowsModifierVk(key_code, scan_code, extended); +#endif + remote_action.k.key_value = key_code; + remote_action.k.scan_code = scan_code; + remote_action.k.extended = extended; std::string target_id = controlled_remote_id_.empty() ? focused_remote_id_ : controlled_remote_id_; @@ -1048,8 +1093,9 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size, if (remote_action.type == ControlType::keyboard) { const int key_code = static_cast(remote_action.k.key_value); const bool is_down = remote_action.k.flag == KeyFlag::key_down; - const std::string response = - SendCrossDeskSecureDesktopKeyInput(key_code, is_down, 1000); + const std::string response = SendCrossDeskSecureDesktopKeyInput( + key_code, is_down, remote_action.k.scan_code, + remote_action.k.extended, 1000); auto json = nlohmann::json::parse(response, nullptr, false); if (json.is_discarded() || !json.value("ok", false)) { LogSecureDesktopInputBlocked( @@ -1076,7 +1122,8 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size, render->keyboard_capturer_) { render->keyboard_capturer_->SendKeyboardCommand( (int)remote_action.k.key_value, - remote_action.k.flag == KeyFlag::key_down); + remote_action.k.flag == KeyFlag::key_down, remote_action.k.scan_code, + remote_action.k.extended); } else if (remote_action.type == ControlType::display_id && render->screen_capturer_) { render->selected_display_ = remote_action.d; diff --git a/src/service/windows/service_host.cpp b/src/service/windows/service_host.cpp index 1a1cfed..1c8af77 100644 --- a/src/service/windows/service_host.cpp +++ b/src/service/windows/service_host.cpp @@ -313,10 +313,12 @@ std::string QueryNamedPipeMessage(const std::wstring& pipe_name, return std::string(buffer, buffer + bytes_read); } -std::string BuildSecureDesktopKeyboardIpcCommand(int key_code, bool is_down) { +std::string BuildSecureDesktopKeyboardIpcCommand(int key_code, bool is_down, + uint32_t scan_code, + bool extended) { std::ostringstream stream; stream << kSecureDesktopKeyboardIpcCommandPrefix << key_code << ":" - << (is_down ? 1 : 0); + << (is_down ? 1 : 0) << ":" << scan_code << ":" << (extended ? 1 : 0); return stream.str(); } @@ -328,20 +330,27 @@ std::string BuildSecureDesktopMouseIpcCommand(int x, int y, int wheel, return stream.str(); } -std::string BuildSecureInputHelperKeyboardCommand(int key_code, bool is_down) { +std::string BuildSecureInputHelperKeyboardCommand(int key_code, bool is_down, + uint32_t scan_code, + bool extended) { std::ostringstream stream; stream << kCrossDeskSecureInputKeyboardCommandPrefix << key_code << ":" - << (is_down ? 1 : 0); + << (is_down ? 1 : 0) << ":" << scan_code << ":" << (extended ? 1 : 0); return stream.str(); } bool ParseSecureDesktopKeyboardIpcCommand(const std::string& command, - int* key_code_out, - bool* is_down_out) { - if (key_code_out == nullptr || is_down_out == nullptr) { + int* key_code_out, bool* is_down_out, + uint32_t* scan_code_out, + bool* extended_out) { + if (key_code_out == nullptr || is_down_out == nullptr || + scan_code_out == nullptr || extended_out == nullptr) { return false; } + *scan_code_out = 0; + *extended_out = false; + if (command.rfind(kSecureDesktopKeyboardIpcCommandPrefix, 0) != 0) { return false; } @@ -358,13 +367,46 @@ bool ParseSecureDesktopKeyboardIpcCommand(const std::string& command, return false; } - const std::string state = command.substr(separator + 1); + const size_t scan_separator = command.find(':', separator + 1); + const std::string state = + scan_separator == std::string::npos + ? command.substr(separator + 1) + : command.substr(separator + 1, scan_separator - separator - 1); if (state == "1" || state == "down") { *is_down_out = true; + } else if (state == "0" || state == "up") { + *is_down_out = false; + } else { + return false; + } + + if (scan_separator == std::string::npos) { return true; } - if (state == "0" || state == "up") { - *is_down_out = false; + + const size_t extended_separator = command.find(':', scan_separator + 1); + const std::string scan_code_str = + extended_separator == std::string::npos + ? command.substr(scan_separator + 1) + : command.substr(scan_separator + 1, + extended_separator - scan_separator - 1); + try { + *scan_code_out = static_cast(std::stoul(scan_code_str)); + } catch (...) { + return false; + } + + if (extended_separator == std::string::npos) { + return true; + } + + const std::string extended_str = command.substr(extended_separator + 1); + if (extended_str == "1" || extended_str == "true") { + *extended_out = true; + return true; + } + if (extended_str == "0" || extended_str == "false") { + *extended_out = false; return true; } return false; @@ -1712,8 +1754,12 @@ std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command) { } int key_code = 0; bool is_down = false; - if (ParseSecureDesktopKeyboardIpcCommand(normalized, &key_code, &is_down)) { - return SendSecureDesktopKeyboardInput(key_code, is_down); + uint32_t scan_code = 0; + bool extended = false; + if (ParseSecureDesktopKeyboardIpcCommand(normalized, &key_code, &is_down, + &scan_code, &extended)) { + return SendSecureDesktopKeyboardInput(key_code, is_down, scan_code, + extended); } return BuildErrorJson("unknown_command"); } @@ -1928,8 +1974,8 @@ std::string CrossDeskServiceHost::SendSecureAttentionSequence() { return "{\"ok\":true,\"sent\":\"sas\"}"; } -std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput(int key_code, - bool is_down) { +std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( + int key_code, bool is_down, uint32_t scan_code, bool extended) { RefreshSessionState(); ReapSecureInputHelper(); EnsureSessionHelper(); @@ -1963,7 +2009,9 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput(int key_code, return QueryNamedPipeMessage( GetCrossDeskSecureInputHelperPipeName(target_session_id), - BuildSecureInputHelperKeyboardCommand(key_code, is_down), 1000); + BuildSecureInputHelperKeyboardCommand(key_code, is_down, scan_code, + extended), + 1000); } bool InstallCrossDeskService(const std::wstring& binary_path) { @@ -2176,9 +2224,12 @@ std::string QueryCrossDeskService(const std::string& command, } std::string SendCrossDeskSecureDesktopKeyInput(int key_code, bool is_down, + uint32_t scan_code, + bool extended, DWORD timeout_ms) { - return QueryCrossDeskService( - BuildSecureDesktopKeyboardIpcCommand(key_code, is_down), timeout_ms); + return QueryCrossDeskService(BuildSecureDesktopKeyboardIpcCommand( + key_code, is_down, scan_code, extended), + timeout_ms); } std::string SendCrossDeskSecureDesktopMouseInput(int x, int y, int wheel, diff --git a/src/service/windows/service_host.h b/src/service/windows/service_host.h index 9bf7b89..d0dba77 100644 --- a/src/service/windows/service_host.h +++ b/src/service/windows/service_host.h @@ -9,6 +9,7 @@ #include +#include #include #include #include @@ -60,7 +61,9 @@ class CrossDeskServiceHost { std::string HandleIpcCommand(const std::string& command); std::string BuildStatusResponse(); std::string SendSecureAttentionSequence(); - std::string SendSecureDesktopKeyboardInput(int key_code, bool is_down); + std::string SendSecureDesktopKeyboardInput(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false); static void WINAPI ServiceMain(DWORD argc, LPWSTR* argv); static BOOL WINAPI ConsoleControlHandler(DWORD control_type); @@ -138,6 +141,8 @@ bool StopCrossDeskService(DWORD timeout_ms = 5000); std::string QueryCrossDeskService(const std::string& command, DWORD timeout_ms = 1000); std::string SendCrossDeskSecureDesktopKeyInput(int key_code, bool is_down, + uint32_t scan_code = 0, + bool extended = false, DWORD timeout_ms = 1000); std::string SendCrossDeskSecureDesktopMouseInput(int x, int y, int wheel, int flag, diff --git a/src/service/windows/session_helper_main.cpp b/src/service/windows/session_helper_main.cpp index 7d55859..511196d 100644 --- a/src/service/windows/session_helper_main.cpp +++ b/src/service/windows/session_helper_main.cpp @@ -485,20 +485,56 @@ bool EnsureThreadDesktop(const wchar_t* desktop_name, return true; } -int InjectKeyboardInput(int key_code, bool is_down) { +bool PreferSideSpecificVkInjection(int key_code) { + switch (key_code) { + case VK_LSHIFT: + case VK_RSHIFT: + case VK_LCONTROL: + case VK_RCONTROL: + case VK_LMENU: + case VK_RMENU: + case VK_LWIN: + case VK_RWIN: + return true; + default: + return false; + } +} + +int InjectKeyboardInput(int key_code, bool is_down, uint32_t scan_code, + bool extended) { INPUT input = {0}; input.type = INPUT_KEYBOARD; - input.ki.wVk = static_cast(key_code); - const UINT scan_code = - MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); - if (scan_code != 0) { + const bool prefer_vk = PreferSideSpecificVkInjection(key_code); + const UINT resolved_scan_code = + scan_code != 0 + ? static_cast(scan_code & 0xFF) | (extended ? 0xE000u : 0u) + : MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); + + if (scan_code != 0 && !prefer_vk) { input.ki.wVk = 0; input.ki.wScan = static_cast(scan_code & 0xFF); input.ki.dwFlags |= KEYEVENTF_SCANCODE; - if ((scan_code & 0xFF00) != 0) { + if (extended) { input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } + } else { + input.ki.wVk = static_cast(key_code); + + if (prefer_vk && resolved_scan_code != 0) { + input.ki.wScan = static_cast(resolved_scan_code & 0xFF); + if ((resolved_scan_code & 0xFF00) != 0) { + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + } else if (resolved_scan_code != 0) { + input.ki.wVk = 0; + input.ki.wScan = static_cast(resolved_scan_code & 0xFF); + input.ki.dwFlags |= KEYEVENTF_SCANCODE; + if ((resolved_scan_code & 0xFF00) != 0) { + input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; + } + } } if (!is_down) { @@ -514,11 +550,17 @@ int InjectKeyboardInput(int key_code, bool is_down) { } bool ParseSecureInputKeyboardCommand(const std::string& command, - int* key_code_out, bool* is_down_out) { - if (key_code_out == nullptr || is_down_out == nullptr) { + int* key_code_out, bool* is_down_out, + uint32_t* scan_code_out, + bool* extended_out) { + if (key_code_out == nullptr || is_down_out == nullptr || + scan_code_out == nullptr || extended_out == nullptr) { return false; } + *scan_code_out = 0; + *extended_out = false; + if (command.rfind(crossdesk::kCrossDeskSecureInputKeyboardCommandPrefix, 0) != 0) { return false; @@ -537,13 +579,46 @@ bool ParseSecureInputKeyboardCommand(const std::string& command, return false; } - const std::string state = command.substr(separator + 1); + const size_t scan_separator = command.find(':', separator + 1); + const std::string state = + scan_separator == std::string::npos + ? command.substr(separator + 1) + : command.substr(separator + 1, scan_separator - separator - 1); if (state == "1" || state == "down") { *is_down_out = true; + } else if (state == "0" || state == "up") { + *is_down_out = false; + } else { + return false; + } + + if (scan_separator == std::string::npos) { return true; } - if (state == "0" || state == "up") { - *is_down_out = false; + + const size_t extended_separator = command.find(':', scan_separator + 1); + const std::string scan_code_str = + extended_separator == std::string::npos + ? command.substr(scan_separator + 1) + : command.substr(scan_separator + 1, + extended_separator - scan_separator - 1); + try { + *scan_code_out = static_cast(std::stoul(scan_code_str)); + } catch (...) { + return false; + } + + if (extended_separator == std::string::npos) { + return true; + } + + const std::string extended_str = command.substr(extended_separator + 1); + if (extended_str == "1" || extended_str == "true") { + *extended_out = true; + return true; + } + if (extended_str == "0" || extended_str == "false") { + *extended_out = false; return true; } return false; @@ -807,13 +882,17 @@ std::vector HandleSecureInputHelperCommand( int key_code = 0; bool is_down = false; - if (ParseSecureInputKeyboardCommand(command, &key_code, &is_down)) { - const int inject_result = InjectKeyboardInput(key_code, is_down); + uint32_t scan_code = 0; + bool extended = false; + if (ParseSecureInputKeyboardCommand(command, &key_code, &is_down, &scan_code, + &extended)) { + const int inject_result = + InjectKeyboardInput(key_code, is_down, scan_code, extended); if (inject_result != 0) { LOG_WARN( "Secure input helper SendInput failed for key_code={}, is_down={}, " - "err={}", - key_code, is_down, inject_result); + "scan_code={}, extended={}, err={}", + key_code, is_down, scan_code, extended, inject_result); return BuildTextResponseBytes(BuildErrorJson( "send_input_failed", static_cast(inject_result))); } @@ -823,6 +902,8 @@ std::vector HandleSecureInputHelperCommand( json["injected"] = "keyboard"; json["key_code"] = key_code; json["is_down"] = is_down; + json["scan_code"] = scan_code; + json["extended"] = extended; json["desktop"] = WideToUtf8(GetCurrentThreadDesktopNameW()); return BuildTextResponseBytes(json.dump()); }