From 4797af9260377b07a65c60fc50d111e789761d67 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Sat, 19 Sep 2026 03:56:39 +0800 Subject: [PATCH] [fix] correct Windows remote control session routing and key mapping --- .../features/input/keyboard_controller.cpp | 4 +- .../gui/runtime/windows_service_runtime.cpp | 26 ++++- .../input/keyboard/keyboard_capturer.cpp | 9 +- .../windows/input/mouse/mouse_controller.cpp | 7 +- .../windows/input/windows_input_injector.cpp | 108 ++++++++++++++++++ .../windows/input/windows_input_injector.h | 21 ++++ .../screen_capturer/screen_capturer_win.cpp | 26 ++++- .../secure_desktop_status_poller.h | 3 +- .../windows/service/interactive_desktop.h | 51 +++++++++ .../platform/windows/service/service_host.cpp | 87 +++++++++++--- .../platform/windows/service/service_host.h | 13 ++- .../windows/service/session_helper_main.cpp | 11 ++ apps/desktop/xmake/targets.lua | 9 +- 13 files changed, 334 insertions(+), 41 deletions(-) create mode 100644 apps/desktop/src/platform/windows/input/windows_input_injector.cpp create mode 100644 apps/desktop/src/platform/windows/input/windows_input_injector.h diff --git a/apps/desktop/src/gui/features/input/keyboard_controller.cpp b/apps/desktop/src/gui/features/input/keyboard_controller.cpp index 8b2ca70c..310edde9 100644 --- a/apps/desktop/src/gui/features/input/keyboard_controller.cpp +++ b/apps/desktop/src/gui/features/input/keyboard_controller.cpp @@ -52,6 +52,9 @@ void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t* scan_code_out, if (!scan_code_out || !extended_out) { return; } + if (LookupWindowsKeyMetadataFromVk(key_code, scan_code_out, extended_out)) { + return; + } #if _WIN32 const UINT scan_code = MapVirtualKeyW(static_cast(key_code), MAPVK_VK_TO_VSC_EX); @@ -61,7 +64,6 @@ void PopulateWindowsKeyMetadataFromVk(int key_code, uint32_t* scan_code_out, return; } #endif - LookupWindowsKeyMetadataFromVk(key_code, scan_code_out, extended_out); } #if _WIN32 diff --git a/apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp b/apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp index caf14954..a5562b60 100644 --- a/apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp +++ b/apps/desktop/src/platform/windows/gui/runtime/windows_service_runtime.cpp @@ -10,6 +10,7 @@ #include "rd_log.h" #if _WIN32 +#include "interactive_desktop.h" #include "interactive_state.h" #include "service_host.h" #endif @@ -19,6 +20,7 @@ namespace { #if _WIN32 struct WindowsServiceInteractiveStatus { bool available = false; + bool session_mismatch = false; bool sas_secure_desktop_grace_active = false; unsigned int error_code = 0; std::string interactive_stage; @@ -65,6 +67,14 @@ bool QueryWindowsServiceInteractiveStatus( if (!status->available) { status->error = json.value("error", std::string("service_unavailable")); status->error_code = json.value("code", 0u); + status->session_mismatch = status->error == "service_session_mismatch"; + return true; + } + + if (!IsCurrentProcessSession(json.value("active_session_id", 0xFFFFFFFFu))) { + status->available = false; + status->session_mismatch = true; + status->error = "service_session_mismatch"; return true; } @@ -145,6 +155,9 @@ void GuiRuntime::HandleWindowsServiceIntegration() { WindowsServiceInteractiveStatus status; const bool status_ok = QueryWindowsServiceInteractiveStatus(&status); + const bool user_desktop_recovered = + !status.available && IsCurrentSessionUserDesktopActive(); + if (user_desktop_recovered) status.interactive_stage = "user-desktop"; WindowsServiceInteractiveStatus broadcast_status = status; const bool previous_secure_desktop_interaction = IsSecureDesktopInteractionRequired(local_interactive_stage_); @@ -167,8 +180,11 @@ void GuiRuntime::HandleWindowsServiceIntegration() { local_interactive_stage_ = status.interactive_stage; optimistic_windows_secure_desktop_until_tick_ = 0; } - } else if (!previous_secure_desktop_interaction) { - local_interactive_stage_.clear(); + } else if (status.session_mismatch || user_desktop_recovered || + !previous_secure_desktop_interaction) { + // A verified local desktop or a different service session invalidates the + // cached secure stage. An unexplained IPC failure alone still does not. + local_interactive_stage_ = status.interactive_stage; optimistic_windows_secure_desktop_until_tick_ = 0; } @@ -184,9 +200,9 @@ void GuiRuntime::HandleWindowsServiceIntegration() { LOG_INFO( "Local Windows service available for secure desktop integration"); } else if (IsTransientWindowsServiceStatusError(status.error)) { - LOG_INFO("Local Windows service temporarily unavailable, keeping last " - "secure desktop state: error={}, code={}", - status.error, status.error_code); + LOG_INFO("Local Windows service temporarily unavailable: error={}, " + "code={}, local_stage='{}'", + status.error, status.error_code, local_interactive_stage_); } else { LOG_WARN( "Local Windows service unavailable, secure desktop integration " diff --git a/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp b/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp index 70f5e22b..32b4ed24 100644 --- a/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp +++ b/apps/desktop/src/platform/windows/input/keyboard/keyboard_capturer.cpp @@ -3,7 +3,9 @@ #include #include "rd_log.h" +#include "windows_input_injector.h" #include "windows_input_marker.h" +#include "windows_key_metadata.h" namespace crossdesk { namespace { @@ -264,6 +266,11 @@ void PlatformKeyboardCapturer::HandleRawInput(HRAWINPUT raw_input_handle) { // Apply remote keyboard commands to the local machine. int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, uint32_t scan_code, bool extended) { + if (scan_code == 0) { + // Some layouts omit the E0 prefix for navigation keys in MapVirtualKey. + // Preserve explicit metadata, including non-extended numpad navigation. + LookupWindowsKeyMetadataFromVk(key_code, &scan_code, &extended); + } INPUT input = {0}; input.type = INPUT_KEYBOARD; input.ki.dwExtraInfo = @@ -304,7 +311,7 @@ int PlatformKeyboardCapturer::SendKeyboardCommand(int key_code, bool is_down, input.ki.dwFlags |= KEYEVENTF_KEYUP; } - const UINT sent = SendInput(1, &input, sizeof(INPUT)); + const UINT sent = SendInputOnUserDesktop(input); if (sent != 1) { LOG_WARN("SendInput failed for key_code={}, is_down={}, err={}", key_code, is_down, GetLastError()); diff --git a/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp index 3826c108..6da8539f 100644 --- a/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp +++ b/apps/desktop/src/platform/windows/input/mouse/mouse_controller.cpp @@ -6,9 +6,10 @@ #include #include -#include "../../windows_thread_dpi.h" #include "rd_log.h" +#include "windows_input_injector.h" #include "windows_input_marker.h" +#include "windows_thread_dpi.h" namespace crossdesk { @@ -33,7 +34,7 @@ int PlatformMouseController::ReleasePressedButtons() { if (pressed & 1) input.mi.dwFlags |= MOUSEEVENTF_LEFTUP; if (pressed & 2) input.mi.dwFlags |= MOUSEEVENTF_RIGHTUP; if (pressed & 4) input.mi.dwFlags |= MOUSEEVENTF_MIDDLEUP; - if (SendInput(1, &input, sizeof(INPUT)) != 1) { + if (SendInputOnUserDesktop(input) != 1) { LOG_WARN("Release remote mouse buttons failed, error={}", GetLastError()); return -1; } @@ -114,7 +115,7 @@ int PlatformMouseController::SendMouseCommand(RemoteAction remote_action, ip.mi.dwFlags |= MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK; ip.mi.dwExtraInfo = kInjectedMouseInputMarker; - const UINT sent = SendInput(1, &ip, sizeof(INPUT)); + const UINT sent = SendInputOnUserDesktop(ip); if (sent != 1) { LOG_WARN("SendInput failed for mouse x={}, y={}, wheel={}, flag={}, err={}", ip.mi.dx, ip.mi.dy, remote_action.m.s, diff --git a/apps/desktop/src/platform/windows/input/windows_input_injector.cpp b/apps/desktop/src/platform/windows/input/windows_input_injector.cpp new file mode 100644 index 00000000..10b8c2b6 --- /dev/null +++ b/apps/desktop/src/platform/windows/input/windows_input_injector.cpp @@ -0,0 +1,108 @@ +#include "windows_input_injector.h" + +#include +#include +#include +#include +#include +#include + +#include "interactive_desktop.h" +#include "rd_log.h" + +namespace crossdesk { +namespace { + +DWORD InjectOnUserDesktop(INPUT input) { + if (!IsCurrentSessionUnlocked()) { + return ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION; + } + ScopedInteractiveDesktop desktop; + if (!desktop.Bind(L"")) { + const DWORD error = GetLastError(); + return error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE; + } + if (!desktop.IsReceivingInput()) { + return ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION; + } + // Lock screens and UAC still require the session-checked service path. + if (_wcsicmp(desktop.name().c_str(), L"Default") != 0) { + return ERROR_ACCESS_DENIED; + } + SetLastError(ERROR_SUCCESS); + if (SendInput(1, &input, sizeof(INPUT)) == 1) return ERROR_SUCCESS; + const DWORD error = GetLastError(); + return error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE; +} + +class UserDesktopInputWorker { + public: + UserDesktopInputWorker() : thread_([this] { Run(); }) {} + + ~UserDesktopInputWorker() { + { + std::lock_guard lock(mutex_); + stopping_ = true; + } + wake_.notify_one(); + thread_.join(); + } + + DWORD Send(INPUT input) { + std::packaged_task task( + [input] { return InjectOnUserDesktop(input); }); + auto result = task.get_future(); + { + std::lock_guard lock(mutex_); + pending_.push(std::move(task)); + } + wake_.notify_one(); + return result.get(); + } + + private: + void Run() { + std::unique_lock lock(mutex_); + for (;;) { + wake_.wait(lock, [this] { return stopping_ || !pending_.empty(); }); + if (pending_.empty()) return; + auto task = std::move(pending_.front()); + pending_.pop(); + lock.unlock(); + task(); + lock.lock(); + } + } + + std::mutex mutex_; + std::condition_variable wake_; + std::queue> pending_; + bool stopping_ = false; + std::thread thread_; +}; + +} // namespace + +UINT SendInputOnUserDesktop(const INPUT& input) { + static UserDesktopInputWorker worker; + const DWORD error = worker.Send(input); + if (error != ERROR_SUCCESS) { + // Include session identity even when Windows reports no useful SendInput + // error (for example UIPI). Keep repeated mouse-move failures bounded. + static std::mutex log_mutex; + static ULONGLONG last_log_tick = 0; + std::lock_guard lock(log_mutex); + const ULONGLONG now = GetTickCount64(); + if (last_log_tick == 0 || now - last_log_tick >= 2000) { + DWORD session_id = 0xFFFFFFFF; + ProcessIdToSessionId(GetCurrentProcessId(), &session_id); + LOG_WARN("User desktop input unavailable: session_id={}, error={}", + session_id, error); + last_log_tick = now; + } + } + SetLastError(error); + return error == ERROR_SUCCESS ? 1 : 0; +} + +} // namespace crossdesk diff --git a/apps/desktop/src/platform/windows/input/windows_input_injector.h b/apps/desktop/src/platform/windows/input/windows_input_injector.h new file mode 100644 index 00000000..c36ac234 --- /dev/null +++ b/apps/desktop/src/platform/windows/input/windows_input_injector.h @@ -0,0 +1,21 @@ +/* + * @Author: DI JUNKUN + * @Date: 2026-09-19 + * Copyright (c) 2026 by DI JUNKUN, All Rights Reserved. + */ + +#ifndef _WINDOWS_INPUT_INJECTOR_H_ +#define _WINDOWS_INPUT_INJECTOR_H_ + +#include + +namespace crossdesk { + +// Executes on a worker without windows or hooks, attached to this process's +// current user input desktop. Returns a SendInput count and preserves the +// worker's failure code in the calling thread's last-error value. +UINT SendInputOnUserDesktop(const INPUT& input); + +} // namespace crossdesk + +#endif \ No newline at end of file diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp index 881915ee..aa558155 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp @@ -18,6 +18,7 @@ #include "captured_nv12_frame.h" #include "captured_cursor_state.h" #include "dxgi_cursor_state.h" +#include "interactive_desktop.h" #include "interactive_state.h" #include "named_pipe_deadline.h" #include "rd_log.h" @@ -269,7 +270,7 @@ bool ParseSecureDesktopFrameResponse(const std::vector& response, return true; } -SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() { +SecureDesktopServiceStatus ReadSecureDesktopServiceStatus() { SecureDesktopServiceStatus status; std::vector response; DWORD error_code = 0; @@ -289,6 +290,14 @@ SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() { if (!status.service_available) { status.error = json.value("error", std::string("service_unavailable")); status.error_code = json.value("code", 0u); + status.desktop_state_known = status.error == "service_session_mismatch"; + return status; + } + + if (!IsCurrentProcessSession(json.value("active_session_id", 0xFFFFFFFFu))) { + status.service_available = false; + status.desktop_state_known = true; + status.error = "service_session_mismatch"; return status; } @@ -326,6 +335,16 @@ SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() { return status; } +SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() { + auto status = ReadSecureDesktopServiceStatus(); + if (!status.service_available && IsCurrentSessionUserDesktopActive()) { + status.desktop_state_known = true; + status.capture_active = false; + status.interactive_stage = "user-desktop"; + } + return status; +} + bool QuerySecureDesktopHelperCommand(DWORD session_id, const std::string& command, std::vector* response_out, @@ -1262,9 +1281,8 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { } else if (IsTransientWindowsServiceStatusError(status.error)) { LOG_INFO( "Windows capturer secure desktop service temporarily " - "unavailable; " - "keeping last capture state: error={}, code={}", - status.error, status.error_code); + "unavailable: error={}, code={}, capture_active={}", + status.error, status.error_code, status.capture_active); } else { LOG_WARN( "Windows capturer secure desktop service unavailable: " diff --git a/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_status_poller.h b/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_status_poller.h index a792b7b9..5cb82290 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_status_poller.h +++ b/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_status_poller.h @@ -21,6 +21,7 @@ namespace crossdesk { struct SecureDesktopServiceStatus { bool service_available = false; + bool desktop_state_known = false; bool capture_active = false; bool helper_running = false; uint32_t helper_process_id = 0; @@ -31,7 +32,7 @@ struct SecureDesktopServiceStatus { std::string error; void UpdateFrom(SecureDesktopServiceStatus latest) { - if (latest.service_available) { + if (latest.service_available || latest.desktop_state_known) { *this = std::move(latest); } else { // Failed IPC says nothing about the desktop. Retain the last confirmed diff --git a/apps/desktop/src/platform/windows/service/interactive_desktop.h b/apps/desktop/src/platform/windows/service/interactive_desktop.h index 518b165b..54b1b09e 100644 --- a/apps/desktop/src/platform/windows/service/interactive_desktop.h +++ b/apps/desktop/src/platform/windows/service/interactive_desktop.h @@ -8,6 +8,7 @@ #define _INTERACTIVE_DESKTOP_H_ #include +#include #include @@ -33,6 +34,52 @@ inline std::wstring GetDesktopNameW(HDESK desktop) { return name; } +inline bool IsDesktopReceivingInput(HDESK desktop) { + BOOL receiving_input = FALSE; + return desktop != nullptr && + GetUserObjectInformationW(desktop, UOI_IO, &receiving_input, + sizeof(receiving_input), nullptr) && + receiving_input != FALSE; +} + +inline bool IsCurrentProcessSession(DWORD session_id) { + DWORD process_session_id = 0xFFFFFFFF; + return session_id != 0xFFFFFFFF && + ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id) && + session_id == process_session_id; +} + +inline bool IsCurrentSessionUnlocked() { + DWORD session_id = 0xFFFFFFFF; + if (!ProcessIdToSessionId(GetCurrentProcessId(), &session_id)) return false; + PWTSINFOEXW info = nullptr; + DWORD bytes = 0; + if (!WTSQuerySessionInformationW( + WTS_CURRENT_SERVER_HANDLE, session_id, WTSSessionInfoEx, + reinterpret_cast(&info), &bytes)) { + return false; + } + const bool unlocked = + info != nullptr && bytes >= sizeof(WTSINFOEXW) && info->Level == 1 && + info->Data.WTSInfoExLevel1.SessionState == WTSActive && + info->Data.WTSInfoExLevel1.SessionFlags == WTS_SESSIONSTATE_UNLOCK; + if (info != nullptr) WTSFreeMemory(info); + return unlocked; +} + +inline bool IsCurrentSessionUserDesktopActive() { + // LockApp can be visible on Default while the session is still locked. + if (!IsCurrentSessionUnlocked()) return false; + HDESK desktop = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS); + if (desktop == nullptr) return false; + // OpenInputDesktop also succeeds in disconnected RDP sessions. The name + // alone does not tell us whether this desktop can currently receive input. + const bool active = IsDesktopReceivingInput(desktop) && + _wcsicmp(GetDesktopNameW(desktop).c_str(), L"Default") == 0; + CloseDesktop(desktop); + return active; +} + // Use only on worker threads without windows or hooks. Restore the original // desktop before closing the handle: Windows cannot close a desktop in use. class ScopedInteractiveDesktop { @@ -88,6 +135,10 @@ class ScopedInteractiveDesktop { return !name_.empty() && _wcsicmp(name_.c_str(), input_name.c_str()) == 0; } + bool IsReceivingInput() const { + return IsDesktopReceivingInput(desktop_); + } + const std::wstring& name() const { return name_; } private: diff --git a/apps/desktop/src/platform/windows/service/service_host.cpp b/apps/desktop/src/platform/windows/service/service_host.cpp index b774cd88..f907270e 100644 --- a/apps/desktop/src/platform/windows/service/service_host.cpp +++ b/apps/desktop/src/platform/windows/service/service_host.cpp @@ -1313,8 +1313,14 @@ void CrossDeskServiceHost::IpcServerLoop() { DWORD bytes_read = 0; if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) && bytes_read > 0) { - std::string response = - HandleIpcCommand(std::string(buffer, buffer + bytes_read)); + ULONG client_session_id = 0xFFFFFFFF; + // Obtain identity from Windows, never from caller-supplied JSON. A GUI + // left in an RDP session must not control a different console session. + const std::string response = + GetNamedPipeClientSessionId(pipe, &client_session_id) + ? HandleIpcCommand(std::string(buffer, buffer + bytes_read), + client_session_id) + : BuildErrorJson("client_session_query_failed", GetLastError()); DWORD bytes_written = 0; WriteFile(pipe, response.data(), static_cast(response.size()), &bytes_written, nullptr); @@ -1329,7 +1335,13 @@ void CrossDeskServiceHost::IpcServerLoop() { void CrossDeskServiceHost::RefreshSessionState() { std::lock_guard lock(state_mutex_); + const DWORD previous_session_id = active_session_id_; active_session_id_ = WTSGetActiveConsoleSessionId(); + if (active_session_id_ != previous_session_id) { + ResetSessionHelperReportedStateLocked("session_changed", 0); + sas_secure_desktop_until_tick_ = 0; + sas_secure_desktop_seen_ = false; + } DWORD process_session_id = 0xFFFFFFFF; if (ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id)) { process_session_id_ = process_session_id; @@ -1931,6 +1943,14 @@ void CrossDeskServiceHost::RefreshSessionHelperReportedState() { } std::lock_guard lock(state_mutex_); + if (target_session_id != active_session_id_ || + target_session_id != session_helper_session_id_ || + json.value("session_id", static_cast(0xFFFFFFFF)) != + target_session_id || + json.value("process_id", 0u) != session_helper_process_id_) { + ResetSessionHelperReportedStateLocked("helper_session_mismatch", 0); + return; + } session_helper_status_ok_ = true; session_helper_status_error_.clear(); session_helper_status_error_code_ = 0; @@ -1969,7 +1989,13 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type, std::lock_guard lock(state_mutex_); last_session_event_type_ = event_type; last_session_event_session_id_ = session_id; + const DWORD previous_session_id = active_session_id_; active_session_id_ = WTSGetActiveConsoleSessionId(); + if (active_session_id_ != previous_session_id) { + ResetSessionHelperReportedStateLocked("session_changed", 0); + sas_secure_desktop_until_tick_ = 0; + sas_secure_desktop_seen_ = false; + } DWORD process_session_id = 0xFFFFFFFF; if (ProcessIdToSessionId(GetCurrentProcessId(), &process_session_id)) { process_session_id_ = process_session_id; @@ -1988,10 +2014,11 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type, prelogin_ = !username_available || username.empty(); if (!QuerySessionLockState(active_session_id_, &session_locked_)) { - if (event_type == WTS_SESSION_LOCK) { + if (session_id == active_session_id_ && event_type == WTS_SESSION_LOCK) { session_locked_ = true; - } else if (event_type == WTS_SESSION_UNLOCK || - event_type == WTS_SESSION_LOGON) { + } else if (session_id == active_session_id_ && + (event_type == WTS_SESSION_UNLOCK || + event_type == WTS_SESSION_LOGON)) { session_locked_ = false; } else if (logon_ui_visible_ || secure_desktop_active_) { session_locked_ = !prelogin_; @@ -2007,16 +2034,17 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type, } } -std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command) { +std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command, + DWORD client_session_id) { std::string normalized = ToLower(Trim(command)); if (normalized == "ping") { return "{\"ok\":true,\"reply\":\"pong\"}"; } if (normalized == "status") { - return BuildStatusResponse(); + return BuildStatusResponse(client_session_id); } if (normalized == "sas") { - return SendSecureAttentionSequence(); + return SendSecureAttentionSequence(client_session_id); } int key_code = 0; bool is_down = false; @@ -2024,22 +2052,29 @@ std::string CrossDeskServiceHost::HandleIpcCommand(const std::string& command) { bool extended = false; if (ParseSecureDesktopKeyboardIpcCommand(normalized, &key_code, &is_down, &scan_code, &extended)) { - return SendSecureDesktopKeyboardInput(key_code, is_down, scan_code, - extended); + return SendSecureDesktopKeyboardInput(client_session_id, key_code, is_down, + scan_code, extended); } SecureDesktopMouseRequest mouse_request; if (ParseSecureDesktopMouseIpcCommand(normalized, &mouse_request)) { - return SendSecureDesktopMouseInput(mouse_request.x, mouse_request.y, - mouse_request.wheel, - mouse_request.flag); + return SendSecureDesktopMouseInput(client_session_id, mouse_request.x, + mouse_request.y, mouse_request.wheel, + mouse_request.flag); } return BuildErrorJson("unknown_command"); } -std::string CrossDeskServiceHost::BuildStatusResponse() { +std::string CrossDeskServiceHost::BuildStatusResponse(DWORD client_session_id) { ReapSecureInputHelper(); ReapSessionHelper(); RefreshSessionState(); + { + std::lock_guard lock(state_mutex_); + if (client_session_id == 0xFFFFFFFF || + active_session_id_ != client_session_id) { + return BuildErrorJson("service_session_mismatch"); + } + } EnsureSessionHelper(); RefreshSessionHelperReportedState(); bool keep_secure_input_helper = false; @@ -2077,6 +2112,9 @@ std::string CrossDeskServiceHost::BuildStatusResponse() { } std::lock_guard lock(state_mutex_); + if (active_session_id_ != client_session_id) { + return BuildErrorJson("service_session_mismatch"); + } std::wstring username; GetSessionUserName(active_session_id_, &username); std::string username_utf8 = EscapeJsonString(WideToUtf8(username)); @@ -2263,8 +2301,13 @@ std::string CrossDeskServiceHost::BuildStatusResponse() { return stream.str(); } -std::string CrossDeskServiceHost::SendSecureAttentionSequence() { +std::string CrossDeskServiceHost::SendSecureAttentionSequence( + DWORD client_session_id) { RefreshSessionState(); + if (client_session_id == 0xFFFFFFFF || + client_session_id != WTSGetActiveConsoleSessionId()) { + return BuildErrorJson("service_session_mismatch"); + } LOG_INFO("Received SAS request for session_id={}", active_session_id_); SasResult result = SendSasNow(); { @@ -2286,7 +2329,8 @@ std::string CrossDeskServiceHost::SendSecureAttentionSequence() { } std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( - int key_code, bool is_down, uint32_t scan_code, bool extended) { + DWORD client_session_id, int key_code, bool is_down, + uint32_t scan_code, bool extended) { RefreshSessionState(); ReapSecureInputHelper(); EnsureSessionHelper(); @@ -2314,6 +2358,9 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( if (target_session_id == 0xFFFFFFFF) { return BuildErrorJson("no_active_console_session"); } + if (target_session_id != client_session_id) { + return BuildErrorJson("service_session_mismatch"); + } if (!can_inject) { return BuildErrorJson("secure_input_not_active"); } @@ -2336,9 +2383,8 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( 1000); } -std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y, - int wheel, - int flag) { +std::string CrossDeskServiceHost::SendSecureDesktopMouseInput( + DWORD client_session_id, int x, int y, int wheel, int flag) { RefreshSessionState(); ReapSecureInputHelper(); EnsureSessionHelper(); @@ -2366,6 +2412,9 @@ std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y, if (target_session_id == 0xFFFFFFFF) { return BuildErrorJson("no_active_console_session"); } + if (target_session_id != client_session_id) { + return BuildErrorJson("service_session_mismatch"); + } if (!can_inject) { return BuildErrorJson("secure_input_not_active"); } diff --git a/apps/desktop/src/platform/windows/service/service_host.h b/apps/desktop/src/platform/windows/service/service_host.h index 87e8c567..9008b98a 100644 --- a/apps/desktop/src/platform/windows/service/service_host.h +++ b/apps/desktop/src/platform/windows/service/service_host.h @@ -65,13 +65,16 @@ class CrossDeskServiceHost { const std::string& interactive_stage) const; void RefreshSessionHelperReportedState(); void RecordSessionEvent(DWORD event_type, DWORD session_id); - std::string HandleIpcCommand(const std::string& command); - std::string BuildStatusResponse(); - std::string SendSecureAttentionSequence(); - std::string SendSecureDesktopKeyboardInput(int key_code, bool is_down, + std::string HandleIpcCommand(const std::string& command, + DWORD client_session_id); + std::string BuildStatusResponse(DWORD client_session_id); + std::string SendSecureAttentionSequence(DWORD client_session_id); + std::string SendSecureDesktopKeyboardInput(DWORD client_session_id, + int key_code, bool is_down, uint32_t scan_code = 0, bool extended = false); - std::string SendSecureDesktopMouseInput(int x, int y, int wheel, int flag); + std::string SendSecureDesktopMouseInput(DWORD client_session_id, + int x, int y, int wheel, int flag); static void WINAPI ServiceMain(DWORD argc, LPWSTR* argv); static BOOL WINAPI ConsoleControlHandler(DWORD control_type); diff --git a/apps/desktop/src/platform/windows/service/session_helper_main.cpp b/apps/desktop/src/platform/windows/service/session_helper_main.cpp index 3ce9fbdc..535021e9 100644 --- a/apps/desktop/src/platform/windows/service/session_helper_main.cpp +++ b/apps/desktop/src/platform/windows/service/session_helper_main.cpp @@ -28,6 +28,7 @@ #include "windows_cursor_state.h" #include "cursor_draw.h" #include "windows_input_marker.h" +#include "windows_key_metadata.h" namespace { @@ -637,6 +638,13 @@ bool EnsureThreadInteractiveDesktopForStage( desktop_name = L"Winlogon"; } if (desktop->Bind(desktop_name)) { + if (!desktop->IsReceivingInput()) { + if (switch_details != nullptr) { + switch_details->error_code = ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION; + } + SetLastError(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION); + return false; + } if (switch_details != nullptr) { switch_details->target_desktop = WideToUtf8(desktop->name()); switch_details->current_desktop = switch_details->target_desktop; @@ -695,6 +703,9 @@ InputInjectionResult InjectKeyboardInput( input.ki.dwExtraInfo = static_cast( crossdesk::kInjectedKeyboardInputMarker); + if (scan_code == 0) { + crossdesk::LookupWindowsKeyMetadataFromVk(key_code, &scan_code, &extended); + } const bool prefer_vk = PreferSideSpecificVkInjection(key_code); const UINT resolved_scan_code = scan_code != 0 diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index 67769df9..5be86822 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -282,10 +282,14 @@ function setup_targets() if is_os("windows") then add_files("apps/desktop/src/platform/windows/input/mouse/*.cpp", "apps/desktop/src/platform/windows/input/keyboard/*.cpp", + "apps/desktop/src/platform/windows/input/windows_input_injector.cpp", "apps/desktop/src/platform/windows/input/device_controller_factory.cpp") - add_includedirs("apps/desktop/src/platform/windows/input/mouse", + add_includedirs("apps/desktop/src/platform/windows", + "apps/desktop/src/platform/windows/service", + "apps/desktop/src/platform/windows/input/mouse", "apps/desktop/src/platform/windows/input/keyboard", "apps/desktop/src/platform/windows/input") + add_syslinks("Wtsapi32", {public = true}) elseif is_os("macosx") then add_files("apps/desktop/src/platform/macos/input/mouse/*.cpp", "apps/desktop/src/platform/macos/input/keyboard/*.cpp", @@ -473,7 +477,8 @@ function setup_targets() add_includedirs("apps/desktop/src/common", "apps/desktop/src/platform/windows/service", "apps/desktop/src/platform/windows/screen_capturer") - add_includedirs("apps/desktop/src/platform/windows/input") + add_includedirs("apps/desktop/src/platform/windows/input", + "apps/desktop/src/platform/common/input") add_includedirs("libs/wire/include") end