From 665f4e684c093c38a153be31cb6c6ba6856b7ec1 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 26 May 2026 03:26:37 +0800 Subject: [PATCH] [feat] improve Windows secure desktop capture and input handling, refs #77 --- src/gui/render.cpp | 23 +- src/gui/render_callback.cpp | 27 +- .../windows/screen_capturer_win.cpp | 59 ++- .../windows/screen_capturer_win.h | 2 + src/service/windows/interactive_state.h | 5 +- src/service/windows/service_host.cpp | 135 ++++- src/service/windows/service_host.h | 5 +- src/service/windows/session_helper_main.cpp | 495 ++++++++++++++---- tests/windows_service_mouse_ipc_test.cpp | 163 ++++++ xmake/targets.lua | 1 + 10 files changed, 776 insertions(+), 139 deletions(-) diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 736f340..b4467c8 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -88,9 +88,14 @@ struct WindowsServiceInteractiveStatus { }; constexpr uint32_t kWindowsServiceStatusIntervalMs = 1000; -constexpr DWORD kWindowsServiceQueryTimeoutMs = 100; +constexpr DWORD kWindowsServiceQueryTimeoutMs = 500; constexpr DWORD kWindowsServiceSasTimeoutMs = 500; +bool IsTransientWindowsServiceStatusError(const std::string& error) { + return error == "pipe_unavailable" || error == "pipe_connect_failed" || + error == "pipe_read_failed"; +} + RemoteAction BuildWindowsServiceStatusAction( const WindowsServiceInteractiveStatus& status) { RemoteAction action{}; @@ -1938,9 +1943,16 @@ void Render::HandleWindowsServiceIntegration() { WindowsServiceInteractiveStatus status; const bool status_ok = QueryWindowsServiceInteractiveStatus(&status); - local_service_status_received_ = status_ok; + const bool previous_secure_desktop_interaction = + IsSecureDesktopInteractionRequired(local_interactive_stage_); + local_service_status_received_ = + status_ok || previous_secure_desktop_interaction; local_service_available_ = status.available; - local_interactive_stage_ = status.available ? status.interactive_stage : ""; + if (status.available) { + local_interactive_stage_ = status.interactive_stage; + } else if (!previous_secure_desktop_interaction) { + local_interactive_stage_.clear(); + } if (status_ok) { const bool availability_changed = @@ -1953,6 +1965,11 @@ void Render::HandleWindowsServiceIntegration() { if (status.available) { 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); } else { LOG_WARN( "Local Windows service unavailable, secure desktop integration " diff --git a/src/gui/render_callback.cpp b/src/gui/render_callback.cpp index 975c38e..8d6bcb5 100644 --- a/src/gui/render_callback.cpp +++ b/src/gui/render_callback.cpp @@ -317,6 +317,22 @@ void LogSecureDesktopInputBlocked(uint32_t* last_tick, const char* side, "cannot drive the Windows password UI", side != nullptr ? side : "unknown", stage != nullptr ? stage : ""); } + +bool IsTransientSecureDesktopInputFailure(const nlohmann::json& response, + const RemoteAction& action) { + if (!response.is_object()) { + return false; + } + if (response.value("error", std::string()) != "send_input_failed") { + return false; + } + if (response.value("code", 0u) != ERROR_ACCESS_DENIED) { + return false; + } + + return action.type == ControlType::keyboard && + action.k.flag == KeyFlag::key_up; +} #endif } // namespace @@ -492,7 +508,7 @@ int Render::ProcessKeyboardEvent(const SDL_Event& event) { int Render::ProcessMouseEvent(const SDL_Event& event) { controlled_remote_id_ = ""; - RemoteAction remote_action; + RemoteAction remote_action{}; float cursor_x = last_mouse_event.motion.x; float cursor_y = last_mouse_event.motion.y; @@ -1104,7 +1120,6 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size, // remote #if _WIN32 if (render->local_service_status_received_ && - render->local_service_available_ && IsSecureDesktopInteractionRequired(render->local_interactive_stage_)) { if (remote_action.type == ControlType::mouse) { int absolute_x = 0; @@ -1145,6 +1160,14 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size, remote_action.k.extended, 1000); auto json = nlohmann::json::parse(response, nullptr, false); if (json.is_discarded() || !json.value("ok", false)) { + if (!json.is_discarded() && + IsTransientSecureDesktopInputFailure(json, remote_action)) { + LOG_INFO( + "Secure desktop keyboard injection transient failure, " + "key_code={}, is_down={}, response={}", + key_code, is_down, response); + return; + } LogSecureDesktopInputBlocked( &render->last_local_secure_input_block_log_tick_, "local", render->local_interactive_stage_.c_str()); diff --git a/src/screen_capturer/windows/screen_capturer_win.cpp b/src/screen_capturer/windows/screen_capturer_win.cpp index e56046a..3275d10 100644 --- a/src/screen_capturer/windows/screen_capturer_win.cpp +++ b/src/screen_capturer/windows/screen_capturer_win.cpp @@ -29,7 +29,7 @@ namespace { using Json = nlohmann::json; constexpr DWORD kSecureDesktopStatusIntervalMs = 250; -constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 150; +constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 500; constexpr DWORD kSecureDesktopHelperPipeTimeoutMs = 120; constexpr DWORD kSecureDesktopTransientErrorGraceMs = 1500; constexpr DWORD kSecureDesktopTransientErrorLogIntervalMs = 5000; @@ -131,20 +131,28 @@ class WgcPluginCapturer final : public ScreenCapturer { }; std::string BuildSecureCaptureCommand(int left, int top, int width, int height, - bool show_cursor) { + bool show_cursor, + const std::string& stage) { std::ostringstream stream; stream << kCrossDeskSecureInputCaptureCommandPrefix << left << ":" << top << ":" << width << ":" << height << ":" << (show_cursor ? 1 : 0); + if (!stage.empty()) { + stream << ":" << stage; + } return stream.str(); } std::string BuildSecureCaptureStartCommand(int left, int top, int width, int height, bool show_cursor, - int fps) { + int fps, + const std::string& stage) { std::ostringstream stream; stream << kCrossDeskSecureInputCaptureStartCommandPrefix << left << ":" << top << ":" << width << ":" << height << ":" << (show_cursor ? 1 : 0) << ":" << fps; + if (!stage.empty()) { + stream << ":" << stage; + } return stream.str(); } @@ -160,6 +168,11 @@ bool IsTransientSecureDesktopFrameError(const std::string& error_message) { error_message.find("\"error\":\"bitblt_failed\"") != std::string::npos; } +bool IsTransientWindowsServiceStatusError(const std::string& error) { + return error == "pipe_unavailable" || error == "pipe_connect_failed" || + error == "pipe_read_failed"; +} + bool ReadPipeMessage(HANDLE pipe, std::vector* response_out, DWORD* error_code_out = nullptr) { if (response_out == nullptr) { @@ -342,6 +355,7 @@ bool QuerySecureDesktopHelperCommand(DWORD session_id, bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, int width, int height, bool show_cursor, + const std::string& stage, std::vector* nv12_frame_out, int* captured_width_out, int* captured_height_out, @@ -352,7 +366,7 @@ bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, } const std::string command = - BuildSecureCaptureCommand(left, top, width, height, show_cursor); + BuildSecureCaptureCommand(left, top, width, height, show_cursor, stage); std::vector response; if (!QuerySecureDesktopHelperCommand(session_id, command, &response, error_out)) { @@ -686,6 +700,7 @@ void ScreenCapturerWin::StopSecureDesktopSharedCapture(DWORD session_id) { secure_shared_height_ = 0; secure_shared_fps_ = 0; secure_shared_show_cursor_ = true; + secure_shared_stage_.clear(); } bool ScreenCapturerWin::OpenSecureDesktopSharedFrame(DWORD session_id, @@ -815,7 +830,8 @@ bool ScreenCapturerWin::ReadSecureDesktopSharedFrame( bool ScreenCapturerWin::StartSecureDesktopSharedCapture( DWORD session_id, int left, int top, int width, int height, - bool show_cursor, int fps, std::string* error_out) { + const std::string& stage, bool show_cursor, int fps, + std::string* error_out) { const size_t payload_size = static_cast(width) * height * 3 / 2; const size_t mapping_size = sizeof(CrossDeskSecureDesktopSharedFrameHeader) + payload_size; @@ -830,6 +846,7 @@ bool ScreenCapturerWin::StartSecureDesktopSharedCapture( secure_shared_session_id_ == session_id && secure_shared_left_ == left && secure_shared_top_ == top && secure_shared_width_ == width && secure_shared_height_ == height && + secure_shared_stage_ == stage && secure_shared_show_cursor_ == show_cursor && secure_shared_fps_ == fps && OpenSecureDesktopSharedFrame(session_id, mapping_size, error_out)) { return true; @@ -838,7 +855,8 @@ bool ScreenCapturerWin::StartSecureDesktopSharedCapture( StopSecureDesktopSharedCapture(secure_shared_session_id_); const std::string command = - BuildSecureCaptureStartCommand(left, top, width, height, show_cursor, fps); + BuildSecureCaptureStartCommand(left, top, width, height, show_cursor, fps, + stage); std::vector response; if (!QuerySecureDesktopHelperCommand(session_id, command, &response, error_out)) { @@ -861,6 +879,7 @@ bool ScreenCapturerWin::StartSecureDesktopSharedCapture( secure_shared_height_ = height; secure_shared_show_cursor_ = show_cursor; secure_shared_fps_ = fps; + secure_shared_stage_ = stage; if (!OpenSecureDesktopSharedFrame(session_id, mapping_size, error_out)) { StopSecureDesktopSharedCapture(session_id); @@ -907,6 +926,11 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { "Windows capturer secure desktop service available, polling " "session_id={}", status.active_session_id); + } else if (IsTransientWindowsServiceStatusError(status.error)) { + LOG_INFO( + "Windows capturer secure desktop service temporarily unavailable: " + "error={}, code={}", + status.error, status.error_code); } else { LOG_WARN( "Windows capturer secure desktop service unavailable: " @@ -973,8 +997,9 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { : kSecureDesktopCaptureMinFps; if (StartSecureDesktopSharedCapture(status.active_session_id, left, top, - width, height, show_cursor, shared_fps, - &error_message) && + width, height, + status.interactive_stage, show_cursor, + shared_fps, &error_message) && ReadSecureDesktopSharedFrame( static_cast(frame_interval_ms + 20), &secure_frame, &captured_width, &captured_height, &error_message)) { @@ -988,6 +1013,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { if (!frame_delivered && QuerySecureDesktopHelperFrame(status.active_session_id, left, top, width, height, show_cursor, + status.interactive_stage, &secure_frame, &captured_width, &captured_height, &error_message)) { if (cb_orig_ && !secure_frame.empty()) { @@ -1011,10 +1037,19 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { continue; } if (now - last_error_tick >= log_interval) { - LOG_WARN( - "Windows capturer secure desktop frame query failed, stage='{}', " - "session_id={}, error={}", - status.interactive_stage, status.active_session_id, error_message); + if (transient_error) { + LOG_INFO( + "Windows capturer secure desktop transient frame query failed, " + "stage='{}', session_id={}, error={}", + status.interactive_stage, status.active_session_id, + error_message); + } else { + LOG_WARN( + "Windows capturer secure desktop frame query failed, stage='{}', " + "session_id={}, error={}", + status.interactive_stage, status.active_session_id, + error_message); + } last_error_tick = now; } } diff --git a/src/screen_capturer/windows/screen_capturer_win.h b/src/screen_capturer/windows/screen_capturer_win.h index e86b583..0f9e956 100644 --- a/src/screen_capturer/windows/screen_capturer_win.h +++ b/src/screen_capturer/windows/screen_capturer_win.h @@ -71,6 +71,7 @@ class ScreenCapturerWin : public ScreenCapturer { int secure_shared_height_ = 0; int secure_shared_fps_ = 0; bool secure_shared_show_cursor_ = true; + std::string secure_shared_stage_; bool secure_shared_capture_started_ = false; void BuildCanonicalFromImpl(); @@ -81,6 +82,7 @@ class ScreenCapturerWin : public ScreenCapturer { std::string* display_name); bool StartSecureDesktopSharedCapture(DWORD session_id, int left, int top, int width, int height, + const std::string& stage, bool show_cursor, int fps, std::string* error_out); void StopSecureDesktopSharedCapture(DWORD session_id); diff --git a/src/service/windows/interactive_state.h b/src/service/windows/interactive_state.h index 0a2a9cd..08dc570 100644 --- a/src/service/windows/interactive_state.h +++ b/src/service/windows/interactive_state.h @@ -13,7 +13,8 @@ namespace crossdesk { inline bool IsSecureDesktopInteractionRequired( const std::string& interactive_stage) { - return interactive_stage == "credential-ui" || + return interactive_stage == "lock-screen" || + interactive_stage == "credential-ui" || interactive_stage == "secure-desktop"; } @@ -38,4 +39,4 @@ inline bool ShouldNormalizeUnlockToUserDesktop( } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/src/service/windows/service_host.cpp b/src/service/windows/service_host.cpp index fc18652..6a0bf76 100644 --- a/src/service/windows/service_host.cpp +++ b/src/service/windows/service_host.cpp @@ -262,8 +262,8 @@ bool GrantCrossDeskServiceStartAccessToAuthenticatedUsers(SC_HANDLE service) { std::string QueryNamedPipeMessage(const std::wstring& pipe_name, const std::string& command, DWORD timeout_ms) { - constexpr int kPipeConnectRetryCount = 3; constexpr DWORD kPipeConnectRetryDelayMs = 15; + const ULONGLONG deadline_tick = GetTickCount64() + timeout_ms; auto is_transient_pipe_error = [](DWORD error) { return error == ERROR_FILE_NOT_FOUND || error == ERROR_PIPE_BUSY || @@ -271,12 +271,23 @@ std::string QueryNamedPipeMessage(const std::wstring& pipe_name, }; HANDLE pipe = INVALID_HANDLE_VALUE; - for (int attempt = 0; attempt < kPipeConnectRetryCount; ++attempt) { - if (!WaitNamedPipeW(pipe_name.c_str(), timeout_ms)) { + DWORD last_error = ERROR_SEM_TIMEOUT; + while (GetTickCount64() <= deadline_tick) { + const ULONGLONG now = GetTickCount64(); + const DWORD wait_timeout = + deadline_tick > now + ? static_cast((std::min)( + deadline_tick - now, static_cast(MAXDWORD))) + : 0; + + if (!WaitNamedPipeW(pipe_name.c_str(), wait_timeout)) { const DWORD error = GetLastError(); - if (attempt + 1 < kPipeConnectRetryCount && - is_transient_pipe_error(error)) { - Sleep(kPipeConnectRetryDelayMs); + last_error = error; + const ULONGLONG retry_tick = GetTickCount64(); + if (is_transient_pipe_error(error) && retry_tick < deadline_tick) { + Sleep(static_cast((std::min)( + static_cast(kPipeConnectRetryDelayMs), + deadline_tick - retry_tick))); continue; } return BuildErrorJson("pipe_unavailable", error); @@ -289,14 +300,21 @@ std::string QueryNamedPipeMessage(const std::wstring& pipe_name, } const DWORD error = GetLastError(); - if (attempt + 1 < kPipeConnectRetryCount && - is_transient_pipe_error(error)) { - Sleep(kPipeConnectRetryDelayMs); + last_error = error; + const ULONGLONG retry_tick = GetTickCount64(); + if (is_transient_pipe_error(error) && retry_tick < deadline_tick) { + Sleep(static_cast((std::min)( + static_cast(kPipeConnectRetryDelayMs), + deadline_tick - retry_tick))); continue; } return BuildErrorJson("pipe_connect_failed", error); } + if (pipe == INVALID_HANDLE_VALUE) { + return BuildErrorJson("pipe_unavailable", last_error); + } + DWORD pipe_mode = PIPE_READMODE_MESSAGE; SetNamedPipeHandleState(pipe, &pipe_mode, nullptr, nullptr); @@ -337,20 +355,27 @@ std::string BuildSecureDesktopMouseIpcCommand(int x, int y, int wheel, return stream.str(); } -std::string BuildSecureInputHelperKeyboardCommand(int key_code, bool is_down, - uint32_t scan_code, - bool extended) { +std::string BuildSecureInputHelperKeyboardCommand( + int key_code, bool is_down, uint32_t scan_code, bool extended, + const std::string& interactive_stage) { std::ostringstream stream; stream << kCrossDeskSecureInputKeyboardCommandPrefix << key_code << ":" << (is_down ? 1 : 0) << ":" << scan_code << ":" << (extended ? 1 : 0); + if (!interactive_stage.empty()) { + stream << ":" << interactive_stage; + } return stream.str(); } -std::string BuildSecureInputHelperMouseCommand(int x, int y, int wheel, - int flag) { +std::string BuildSecureInputHelperMouseCommand( + int x, int y, int wheel, int flag, + const std::string& interactive_stage) { std::ostringstream stream; stream << kCrossDeskSecureInputMouseCommandPrefix << x << ":" << y << ":" << wheel << ":" << flag; + if (!interactive_stage.empty()) { + stream << ":" << interactive_stage; + } return stream.str(); } @@ -565,6 +590,15 @@ const char* DetermineInteractiveStage(bool lock_app_visible, return "user-desktop"; } +std::wstring SecureInputHelperDesktopForStage( + const std::string& interactive_stage) { + if (interactive_stage == "credential-ui" || + interactive_stage == "secure-desktop") { + return L"winsta0\\Winlogon"; + } + return L"winsta0\\default"; +} + bool GetSessionUserName(DWORD session_id, std::wstring* username_out) { if (username_out == nullptr) { return false; @@ -1010,6 +1044,7 @@ int CrossDeskServiceHost::InitializeRuntime() { session_helper_report_input_desktop_.clear(); session_helper_report_interactive_stage_.clear(); secure_input_helper_last_error_.clear(); + secure_input_helper_interactive_stage_.clear(); last_session_event_type_ = 0; last_session_event_session_id_ = active_session_id_; RefreshSessionState(); @@ -1303,6 +1338,17 @@ bool CrossDeskServiceHost::ShouldKeepSecureInputHelperLocked( IsHelperReportingLockScreenLocked()); } +std::string CrossDeskServiceHost::ResolveInteractiveStageLocked() const { + if (!session_helper_report_interactive_stage_.empty()) { + return session_helper_report_interactive_stage_; + } + + return DetermineInteractiveStage( + IsHelperReportingLockScreenLocked(), + session_helper_report_credential_ui_visible_ || logon_ui_visible_, + session_helper_report_secure_desktop_active_ || secure_desktop_active_); +} + std::wstring CrossDeskServiceHost::GetSessionHelperPath() const { std::wstring current_executable = GetCurrentExecutablePathW(); if (current_executable.empty()) { @@ -1392,6 +1438,7 @@ void CrossDeskServiceHost::ReapSecureInputHelper() { secure_input_helper_process_id_ = 0; secure_input_helper_exit_code_ = exit_code; secure_input_helper_started_at_tick_ = 0; + secure_input_helper_interactive_stage_.clear(); } if (process_handle != nullptr) { @@ -1450,6 +1497,7 @@ void CrossDeskServiceHost::StopSecureInputHelper() { secure_input_helper_running_ = false; secure_input_helper_process_id_ = 0; secure_input_helper_started_at_tick_ = 0; + secure_input_helper_interactive_stage_.clear(); } if (stop_event_handle != nullptr) { @@ -1577,7 +1625,8 @@ bool CrossDeskServiceHost::LaunchSessionHelper(DWORD session_id) { return true; } -bool CrossDeskServiceHost::LaunchSecureInputHelper(DWORD session_id) { +bool CrossDeskServiceHost::LaunchSecureInputHelper( + DWORD session_id, const std::string& interactive_stage) { std::wstring helper_path = GetSecureInputHelperPath(); if (helper_path.empty() || !std::filesystem::exists(helper_path)) { std::lock_guard lock(state_mutex_); @@ -1611,7 +1660,10 @@ bool CrossDeskServiceHost::LaunchSecureInputHelper(DWORD session_id) { STARTUPINFOW startup_info{}; startup_info.cb = sizeof(startup_info); - startup_info.lpDesktop = const_cast(L"winsta0\\Winlogon"); + std::wstring secure_input_helper_desktop = + SecureInputHelperDesktopForStage(interactive_stage); + startup_info.lpDesktop = + const_cast(secure_input_helper_desktop.c_str()); PROCESS_INFORMATION process_info{}; BOOL created = FALSE; @@ -1660,10 +1712,14 @@ bool CrossDeskServiceHost::LaunchSecureInputHelper(DWORD session_id) { secure_input_helper_last_error_.clear(); secure_input_helper_running_ = true; secure_input_helper_started_at_tick_ = GetTickCount64(); + secure_input_helper_interactive_stage_ = interactive_stage; } - LOG_INFO("Secure input helper started: session_id={}, pid={}", session_id, - process_info.dwProcessId); + LOG_INFO( + "Secure input helper started: session_id={}, pid={}, stage='{}', " + "desktop='{}'", + session_id, process_info.dwProcessId, interactive_stage, + WideToUtf8(secure_input_helper_desktop)); return true; } @@ -1845,21 +1901,26 @@ std::string CrossDeskServiceHost::BuildStatusResponse() { bool keep_secure_input_helper = false; bool launch_secure_input_helper = false; DWORD secure_input_target_session_id = 0xFFFFFFFF; + std::string secure_input_interactive_stage; { std::lock_guard lock(state_mutex_); secure_input_target_session_id = active_session_id_; + secure_input_interactive_stage = ResolveInteractiveStageLocked(); keep_secure_input_helper = ShouldKeepSecureInputHelperLocked(secure_input_target_session_id); launch_secure_input_helper = keep_secure_input_helper && (!secure_input_helper_running_ || - secure_input_helper_session_id_ != secure_input_target_session_id); + secure_input_helper_session_id_ != secure_input_target_session_id || + secure_input_helper_interactive_stage_ != + secure_input_interactive_stage); } if (keep_secure_input_helper) { if (launch_secure_input_helper) { StopSecureInputHelper(); - LaunchSecureInputHelper(secure_input_target_session_id); + LaunchSecureInputHelper(secure_input_target_session_id, + secure_input_interactive_stage); } } else { StopSecureInputHelper(); @@ -1883,6 +1944,8 @@ std::string CrossDeskServiceHost::BuildStatusResponse() { EscapeJsonString(session_helper_report_input_desktop_); std::string secure_input_helper_last_error = EscapeJsonString(secure_input_helper_last_error_); + std::string secure_input_helper_interactive_stage = + EscapeJsonString(secure_input_helper_interactive_stage_); bool interactive_state_ready = session_helper_status_ok_; const char* interactive_state_source = interactive_state_ready ? "session-helper" : "service-host"; @@ -1909,9 +1972,10 @@ std::string CrossDeskServiceHost::BuildStatusResponse() { std::string interactive_input_desktop = EscapeJsonString( interactive_state_ready ? session_helper_report_input_desktop_ : input_desktop_name_); - std::string interactive_stage = EscapeJsonString(DetermineInteractiveStage( + std::string raw_interactive_stage = DetermineInteractiveStage( interactive_lock_screen_visible, credential_ui_visible, - interactive_secure_desktop_active)); + interactive_secure_desktop_active); + std::string interactive_stage = EscapeJsonString(raw_interactive_stage); std::ostringstream stream; stream << "{\"ok\":true,\"service\":\"CrossDeskService\"" << ",\"active_session_id\":" << active_session_id_ @@ -2005,6 +2069,8 @@ std::string CrossDeskServiceHost::BuildStatusResponse() { << secure_input_helper_last_error << "\"" << ",\"secure_input_helper_last_error_code\":" << secure_input_helper_last_error_code_ + << ",\"secure_input_helper_stage\":\"" + << secure_input_helper_interactive_stage << "\"" << ",\"secure_input_helper_uptime_ms\":" << (secure_input_helper_started_at_tick_ >= started_at_tick_ ? (GetTickCount64() - secure_input_helper_started_at_tick_) @@ -2051,15 +2117,21 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( RefreshSessionState(); ReapSecureInputHelper(); EnsureSessionHelper(); + RefreshSessionHelperReportedState(); DWORD target_session_id = 0xFFFFFFFF; bool helper_running = false; bool can_inject = false; + std::string interactive_stage; { std::lock_guard lock(state_mutex_); target_session_id = active_session_id_; + interactive_stage = ResolveInteractiveStageLocked(); + const bool helper_stage_matches = + secure_input_helper_interactive_stage_ == interactive_stage; helper_running = secure_input_helper_running_ && - secure_input_helper_session_id_ == target_session_id; + secure_input_helper_session_id_ == target_session_id && + helper_stage_matches; can_inject = GetEffectiveSessionLockedLocked() || HasSecureInputUiLocked(); } @@ -2072,7 +2144,7 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( if (!helper_running) { StopSecureInputHelper(); - if (!LaunchSecureInputHelper(target_session_id)) { + if (!LaunchSecureInputHelper(target_session_id, interactive_stage)) { std::lock_guard lock(state_mutex_); return BuildErrorJson(secure_input_helper_last_error_.c_str(), secure_input_helper_last_error_code_); @@ -2082,7 +2154,7 @@ std::string CrossDeskServiceHost::SendSecureDesktopKeyboardInput( return QueryNamedPipeMessage( GetCrossDeskSecureInputHelperPipeName(target_session_id), BuildSecureInputHelperKeyboardCommand(key_code, is_down, scan_code, - extended), + extended, interactive_stage), 1000); } @@ -2092,15 +2164,21 @@ std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y, RefreshSessionState(); ReapSecureInputHelper(); EnsureSessionHelper(); + RefreshSessionHelperReportedState(); DWORD target_session_id = 0xFFFFFFFF; bool helper_running = false; bool can_inject = false; + std::string interactive_stage; { std::lock_guard lock(state_mutex_); target_session_id = active_session_id_; + interactive_stage = ResolveInteractiveStageLocked(); + const bool helper_stage_matches = + secure_input_helper_interactive_stage_ == interactive_stage; helper_running = secure_input_helper_running_ && - secure_input_helper_session_id_ == target_session_id; + secure_input_helper_session_id_ == target_session_id && + helper_stage_matches; can_inject = GetEffectiveSessionLockedLocked() || HasSecureInputUiLocked(); } @@ -2113,7 +2191,7 @@ std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y, if (!helper_running) { StopSecureInputHelper(); - if (!LaunchSecureInputHelper(target_session_id)) { + if (!LaunchSecureInputHelper(target_session_id, interactive_stage)) { std::lock_guard lock(state_mutex_); return BuildErrorJson(secure_input_helper_last_error_.c_str(), secure_input_helper_last_error_code_); @@ -2122,7 +2200,8 @@ std::string CrossDeskServiceHost::SendSecureDesktopMouseInput(int x, int y, return QueryNamedPipeMessage( GetCrossDeskSecureInputHelperPipeName(target_session_id), - BuildSecureInputHelperMouseCommand(x, y, wheel, flag), 1000); + BuildSecureInputHelperMouseCommand(x, y, wheel, flag, interactive_stage), + 1000); } bool InstallCrossDeskService(const std::wstring& binary_path) { diff --git a/src/service/windows/service_host.h b/src/service/windows/service_host.h index efa9baf..b99b7cc 100644 --- a/src/service/windows/service_host.h +++ b/src/service/windows/service_host.h @@ -45,7 +45,8 @@ class CrossDeskServiceHost { bool LaunchSessionHelper(DWORD session_id); void ReapSecureInputHelper(); void StopSecureInputHelper(); - bool LaunchSecureInputHelper(DWORD session_id); + bool LaunchSecureInputHelper(DWORD session_id, + const std::string& interactive_stage); std::wstring GetSessionHelperPath() const; std::wstring GetSessionHelperStopEventName(DWORD session_id) const; std::wstring GetSecureInputHelperPath() const; @@ -56,6 +57,7 @@ class CrossDeskServiceHost { bool IsHelperReportingLockScreenLocked() const; bool HasSecureInputUiLocked() const; bool ShouldKeepSecureInputHelperLocked(DWORD target_session_id) const; + std::string ResolveInteractiveStageLocked() const; void RefreshSessionHelperReportedState(); void RecordSessionEvent(DWORD event_type, DWORD session_id); std::string HandleIpcCommand(const std::string& command); @@ -130,6 +132,7 @@ class CrossDeskServiceHost { std::string session_helper_report_input_desktop_; std::string session_helper_report_interactive_stage_; std::string secure_input_helper_last_error_; + std::string secure_input_helper_interactive_stage_; static CrossDeskServiceHost* instance_; }; diff --git a/src/service/windows/session_helper_main.cpp b/src/service/windows/session_helper_main.cpp index 19670c9..6532cf3 100644 --- a/src/service/windows/session_helper_main.cpp +++ b/src/service/windows/session_helper_main.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "path_manager.h" @@ -58,6 +59,7 @@ struct SecureCaptureRequest { int height = 0; bool show_cursor = true; int fps = 30; + std::string interactive_stage; }; struct SecureMouseRequest { @@ -65,6 +67,7 @@ struct SecureMouseRequest { int y = 0; int wheel = 0; int flag = 0; + std::string interactive_stage; }; struct SecureCaptureBuffers { @@ -126,6 +129,11 @@ void InitializeHelperLogger() { }); } +void EnablePerMonitorDpiAwareness() { + SetProcessDpiAwarenessContext( + DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); +} + std::wstring Utf8ToWide(const std::string& value) { if (value.empty()) { return {}; @@ -447,8 +455,7 @@ void HelperIpcServerLoop(HANDLE stop_event, DWORD session_id, } } -std::wstring GetCurrentThreadDesktopNameW() { - HDESK desktop = GetThreadDesktop(GetCurrentThreadId()); +std::wstring GetDesktopNameW(HDESK desktop) { if (desktop == nullptr) { return L""; } @@ -471,6 +478,14 @@ std::wstring GetCurrentThreadDesktopNameW() { return desktop_name; } +std::wstring GetCurrentThreadDesktopNameW() { + return GetDesktopNameW(GetThreadDesktop(GetCurrentThreadId())); +} + +constexpr ACCESS_MASK kCrossDeskInteractiveDesktopAccess = + DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS | + DESKTOP_SWITCHDESKTOP; + bool EnsureThreadDesktop(const wchar_t* desktop_name, HDESK* opened_desktop_out = nullptr) { if (desktop_name == nullptr) { @@ -483,9 +498,8 @@ bool EnsureThreadDesktop(const wchar_t* desktop_name, return true; } - HDESK desktop = OpenDesktopW(desktop_name, 0, FALSE, - DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | - DESKTOP_READOBJECTS | DESKTOP_SWITCHDESKTOP); + HDESK desktop = + OpenDesktopW(desktop_name, 0, FALSE, kCrossDeskInteractiveDesktopAccess); if (desktop == nullptr) { return false; } @@ -503,6 +517,196 @@ bool EnsureThreadDesktop(const wchar_t* desktop_name, return true; } +bool EnsureThreadInputDesktop(HDESK* opened_desktop_out = nullptr) { + HDESK desktop = + OpenInputDesktop(0, FALSE, kCrossDeskInteractiveDesktopAccess); + if (desktop == nullptr) { + return false; + } + + const std::wstring input_desktop = GetDesktopNameW(desktop); + const std::wstring current_desktop = GetCurrentThreadDesktopNameW(); + if (!input_desktop.empty() && !current_desktop.empty() && + _wcsicmp(input_desktop.c_str(), current_desktop.c_str()) == 0) { + if (opened_desktop_out != nullptr) { + *opened_desktop_out = desktop; + } else { + CloseDesktop(desktop); + } + return true; + } + + if (!SetThreadDesktop(desktop)) { + CloseDesktop(desktop); + return false; + } + + if (opened_desktop_out != nullptr) { + *opened_desktop_out = desktop; + } else { + CloseDesktop(desktop); + } + return true; +} + +bool EnsureThreadInteractiveDesktop(HDESK* opened_desktop_out = nullptr) { + if (opened_desktop_out != nullptr) { + *opened_desktop_out = nullptr; + } + + if (EnsureThreadInputDesktop(opened_desktop_out)) { + return true; + } + const DWORD input_desktop_error = GetLastError(); + + if (EnsureThreadDesktop(L"Winlogon", opened_desktop_out)) { + return true; + } + const DWORD winlogon_error = GetLastError(); + + LOG_WARN( + "Failed to switch secure input helper desktop, input_error={}, " + "winlogon_error={}, current='{}'", + input_desktop_error, winlogon_error, + WideToUtf8(GetCurrentThreadDesktopNameW())); + SetLastError(winlogon_error != ERROR_SUCCESS ? winlogon_error + : input_desktop_error); + return false; +} + +const wchar_t* DesktopNameForInteractiveStage( + const std::string& interactive_stage) { + if (interactive_stage == "credential-ui" || + interactive_stage == "secure-desktop") { + return L"Winlogon"; + } + if (interactive_stage == "lock-screen") { + return L"Default"; + } + return nullptr; +} + +struct DesktopSwitchDetails { + std::string stage; + std::string target_desktop; + std::string current_desktop; + DWORD error_code = ERROR_SUCCESS; +}; + +struct InputInjectionResult { + bool ok = true; + std::string error; + DWORD error_code = ERROR_SUCCESS; + DesktopSwitchDetails desktop; +}; + +DesktopSwitchDetails BuildDesktopSwitchDetails( + const std::string& interactive_stage) { + DesktopSwitchDetails details; + details.stage = interactive_stage; + const wchar_t* desktop_name = + DesktopNameForInteractiveStage(interactive_stage); + details.target_desktop = + desktop_name != nullptr ? WideToUtf8(std::wstring(desktop_name)) + : "input-or-Winlogon"; + details.current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW()); + return details; +} + +InputInjectionResult BuildInputSuccess() { + return {}; +} + +InputInjectionResult BuildInputFailure(const char* error, DWORD error_code, + DesktopSwitchDetails desktop) { + desktop.error_code = error_code; + desktop.current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW()); + + InputInjectionResult result; + result.ok = false; + result.error = error != nullptr ? error : "input_failed"; + result.error_code = + error_code != ERROR_SUCCESS ? error_code : ERROR_GEN_FAILURE; + result.desktop = std::move(desktop); + return result; +} + +Json BuildInputFailureJson(const InputInjectionResult& result) { + Json json; + json["ok"] = false; + json["error"] = result.error; + json["code"] = result.error_code; + json["stage"] = result.desktop.stage; + json["target_desktop"] = result.desktop.target_desktop; + json["current_desktop"] = result.desktop.current_desktop; + return json; +} + +bool EnsureThreadInteractiveDesktopForStage( + const std::string& interactive_stage, + HDESK* opened_desktop_out = nullptr, + DesktopSwitchDetails* switch_details = nullptr) { + if (opened_desktop_out != nullptr) { + *opened_desktop_out = nullptr; + } + + DesktopSwitchDetails local_details = + BuildDesktopSwitchDetails(interactive_stage); + if (switch_details != nullptr) { + *switch_details = local_details; + } + + const wchar_t* desktop_name = + DesktopNameForInteractiveStage(interactive_stage); + if (desktop_name != nullptr) { + if (EnsureThreadDesktop(desktop_name, opened_desktop_out)) { + if (switch_details != nullptr) { + switch_details->current_desktop = + WideToUtf8(GetCurrentThreadDesktopNameW()); + } + return true; + } + + const DWORD error = GetLastError(); + if (switch_details != nullptr) { + switch_details->error_code = error; + switch_details->current_desktop = + WideToUtf8(GetCurrentThreadDesktopNameW()); + } + LOG_WARN( + "Failed to switch secure input helper to stage desktop, stage='{}', " + "desktop='{}', error={}, current='{}'", + interactive_stage, WideToUtf8(std::wstring(desktop_name)), + error, + WideToUtf8(GetCurrentThreadDesktopNameW())); + SetLastError(error); + return false; + } + + if (EnsureThreadInteractiveDesktop(opened_desktop_out)) { + if (switch_details != nullptr) { + switch_details->current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW()); + } + return true; + } + + const DWORD error = GetLastError(); + if (switch_details != nullptr) { + switch_details->error_code = error; + switch_details->current_desktop = WideToUtf8(GetCurrentThreadDesktopNameW()); + } + return false; +} + +struct ScopedDesktopHandle { + HDESK handle = nullptr; + ~ScopedDesktopHandle() { + if (handle != nullptr) { + CloseDesktop(handle); + } + } +}; + bool PreferSideSpecificVkInjection(int key_code) { switch (key_code) { case VK_LSHIFT: @@ -519,8 +723,20 @@ bool PreferSideSpecificVkInjection(int key_code) { } } -int InjectKeyboardInput(int key_code, bool is_down, uint32_t scan_code, - bool extended) { +InputInjectionResult InjectKeyboardInput( + int key_code, bool is_down, uint32_t scan_code, bool extended, + const std::string& interactive_stage) { + ScopedDesktopHandle desktop; + DesktopSwitchDetails desktop_switch; + if (!EnsureThreadInteractiveDesktopForStage(interactive_stage, + &desktop.handle, + &desktop_switch)) { + const DWORD error = GetLastError(); + return BuildInputFailure("switch_interactive_desktop_failed", + error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE, + desktop_switch); + } + INPUT input = {0}; input.type = INPUT_KEYBOARD; @@ -561,16 +777,21 @@ int InjectKeyboardInput(int key_code, bool is_down, uint32_t scan_code, UINT sent = SendInput(1, &input, sizeof(INPUT)); if (sent != 1) { - return static_cast(GetLastError()); + const DWORD error = GetLastError(); + return BuildInputFailure("send_input_failed", + error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE, + desktop_switch); } - return 0; + return BuildInputSuccess(); } bool ParseSecureInputKeyboardCommand(const std::string& command, int* key_code_out, bool* is_down_out, uint32_t* scan_code_out, - bool* extended_out) { + bool* extended_out, + std::string* interactive_stage_out = + nullptr) { if (key_code_out == nullptr || is_down_out == nullptr || scan_code_out == nullptr || extended_out == nullptr) { return false; @@ -578,6 +799,9 @@ bool ParseSecureInputKeyboardCommand(const std::string& command, *scan_code_out = 0; *extended_out = false; + if (interactive_stage_out != nullptr) { + interactive_stage_out->clear(); + } if (command.rfind(crossdesk::kCrossDeskSecureInputKeyboardCommandPrefix, 0) != 0) { @@ -630,7 +854,16 @@ bool ParseSecureInputKeyboardCommand(const std::string& command, return true; } - const std::string extended_str = command.substr(extended_separator + 1); + const size_t stage_separator = command.find(':', extended_separator + 1); + const std::string extended_str = + stage_separator == std::string::npos + ? command.substr(extended_separator + 1) + : command.substr(extended_separator + 1, + stage_separator - extended_separator - 1); + if (stage_separator != std::string::npos && + interactive_stage_out != nullptr) { + *interactive_stage_out = command.substr(stage_separator + 1); + } if (extended_str == "1" || extended_str == "true") { *extended_out = true; return true; @@ -652,6 +885,7 @@ bool ParseSecureInputMouseCommand(const std::string& command, 0) { return false; } + request_out->interactive_stage.clear(); const size_t x_begin = std::strlen(crossdesk::kCrossDeskSecureInputMouseCommandPrefix); @@ -687,7 +921,15 @@ bool ParseSecureInputMouseCommand(const std::string& command, try { request_out->wheel = std::stoi(command.substr(wheel_begin, separator - wheel_begin)); - request_out->flag = std::stoi(command.substr(separator + 1)); + const size_t flag_begin = separator + 1; + const size_t stage_separator = command.find(':', flag_begin); + request_out->flag = std::stoi( + stage_separator == std::string::npos + ? command.substr(flag_begin) + : command.substr(flag_begin, stage_separator - flag_begin)); + if (stage_separator != std::string::npos) { + request_out->interactive_stage = command.substr(stage_separator + 1); + } } catch (...) { return false; } @@ -705,15 +947,17 @@ bool ParseSecureInputCaptureCommand(const std::string& command, 0) { return false; } + request_out->interactive_stage.clear(); const size_t values_begin = std::strlen(crossdesk::kCrossDeskSecureInputCaptureCommandPrefix); int parsed_values[5] = {0}; size_t token_begin = values_begin; + size_t separator = std::string::npos; for (int index = 0; index < 5; ++index) { - const size_t separator = command.find(':', token_begin); - const bool is_last = index == 4; - const size_t token_end = is_last ? command.size() : separator; + separator = command.find(':', token_begin); + const size_t token_end = + separator == std::string::npos ? command.size() : separator; if (token_end == std::string::npos || token_end <= token_begin) { return false; } @@ -733,6 +977,9 @@ bool ParseSecureInputCaptureCommand(const std::string& command, request_out->width = parsed_values[2] & ~1; request_out->height = parsed_values[3] & ~1; request_out->show_cursor = parsed_values[4] != 0; + request_out->interactive_stage = + separator == std::string::npos ? std::string() + : command.substr(token_begin); return request_out->width > 0 && request_out->height > 0; } @@ -746,15 +993,17 @@ bool ParseSecureInputCaptureStartCommand(const std::string& command, crossdesk::kCrossDeskSecureInputCaptureStartCommandPrefix, 0) != 0) { return false; } + request_out->interactive_stage.clear(); const size_t values_begin = std::strlen( crossdesk::kCrossDeskSecureInputCaptureStartCommandPrefix); int parsed_values[6] = {0}; size_t token_begin = values_begin; + size_t separator = std::string::npos; for (int index = 0; index < 6; ++index) { - const size_t separator = command.find(':', token_begin); - const bool is_last = index == 5; - const size_t token_end = is_last ? command.size() : separator; + separator = command.find(':', token_begin); + const size_t token_end = + separator == std::string::npos ? command.size() : separator; if (token_end == std::string::npos || token_end <= token_begin) { return false; } @@ -775,56 +1024,107 @@ bool ParseSecureInputCaptureStartCommand(const std::string& command, request_out->height = parsed_values[3] & ~1; request_out->show_cursor = parsed_values[4] != 0; request_out->fps = parsed_values[5] > 0 ? parsed_values[5] : 30; + request_out->interactive_stage = + separator == std::string::npos ? std::string() + : command.substr(token_begin); return request_out->width > 0 && request_out->height > 0; } -int InjectMouseInput(const SecureMouseRequest& request) { - SetCursorPos(request.x, request.y); - - INPUT input = {0}; - input.type = INPUT_MOUSE; - switch (request.flag) { - case 1: - input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; - break; - case 2: - input.mi.dwFlags = MOUSEEVENTF_LEFTUP; - break; - case 3: - input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; - break; - case 4: - input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; - break; - case 5: - input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; - break; - case 6: - input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; - break; - case 7: - input.mi.dwFlags = MOUSEEVENTF_WHEEL; - input.mi.mouseData = request.wheel * 120; - break; - case 8: - input.mi.dwFlags = MOUSEEVENTF_HWHEEL; - input.mi.mouseData = request.wheel * 120; - break; - default: - input.mi.dwFlags = 0; - break; - } - - if (input.mi.dwFlags == 0) { +LONG NormalizeAbsoluteMouseCoordinate(int value, int origin, int size) { + if (size <= 1) { return 0; } - UINT sent = SendInput(1, &input, sizeof(INPUT)); - if (sent != 1) { - return static_cast(GetLastError()); + const int clamped_value = + (std::max)(origin, (std::min)(value, origin + size - 1)); + const long long relative_value = + static_cast(clamped_value - origin) * 65535; + return static_cast(relative_value / (size - 1)); +} + +INPUT BuildAbsoluteMouseMoveInput(int x, int y) { + INPUT input = {0}; + input.type = INPUT_MOUSE; + + const int virtual_left = GetSystemMetrics(SM_XVIRTUALSCREEN); + const int virtual_top = GetSystemMetrics(SM_YVIRTUALSCREEN); + const int virtual_width = GetSystemMetrics(SM_CXVIRTUALSCREEN); + const int virtual_height = GetSystemMetrics(SM_CYVIRTUALSCREEN); + + input.mi.dx = + NormalizeAbsoluteMouseCoordinate(x, virtual_left, virtual_width); + input.mi.dy = + NormalizeAbsoluteMouseCoordinate(y, virtual_top, virtual_height); + input.mi.dwFlags = + MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK; + return input; +} + +InputInjectionResult InjectMouseInput(const SecureMouseRequest& request) { + ScopedDesktopHandle desktop; + DesktopSwitchDetails desktop_switch; + if (!EnsureThreadInteractiveDesktopForStage(request.interactive_stage, + &desktop.handle, + &desktop_switch)) { + const DWORD error = GetLastError(); + return BuildInputFailure("switch_interactive_desktop_failed", + error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE, + desktop_switch); } - return 0; + std::vector inputs; + inputs.push_back(BuildAbsoluteMouseMoveInput(request.x, request.y)); + + INPUT action_input = {0}; + action_input.type = INPUT_MOUSE; + switch (request.flag) { + case 0: + break; + case 1: + action_input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; + break; + case 2: + action_input.mi.dwFlags = MOUSEEVENTF_LEFTUP; + break; + case 3: + action_input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; + break; + case 4: + action_input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; + break; + case 5: + action_input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; + break; + case 6: + action_input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; + break; + case 7: + action_input.mi.dwFlags = MOUSEEVENTF_WHEEL; + action_input.mi.mouseData = request.wheel * 120; + break; + case 8: + action_input.mi.dwFlags = MOUSEEVENTF_HWHEEL; + action_input.mi.mouseData = request.wheel * 120; + break; + default: + action_input.mi.dwFlags = 0; + break; + } + + if (action_input.mi.dwFlags != 0) { + inputs.push_back(action_input); + } + + UINT sent = + SendInput(static_cast(inputs.size()), inputs.data(), sizeof(INPUT)); + if (sent != inputs.size()) { + const DWORD error = GetLastError(); + return BuildInputFailure("send_input_failed", + error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE, + desktop_switch); + } + + return BuildInputSuccess(); } std::vector BuildTextResponseBytes(const std::string& response) { @@ -838,6 +1138,15 @@ std::vector CaptureSecureDesktopFrame( return BuildTextResponseBytes(BuildErrorJson("invalid_capture_buffers")); } + ScopedDesktopHandle desktop; + if (!EnsureThreadInteractiveDesktopForStage(request.interactive_stage, + &desktop.handle)) { + const DWORD error = GetLastError(); + return BuildTextResponseBytes(BuildErrorJson( + "switch_interactive_desktop_failed", + error != ERROR_SUCCESS ? error : ERROR_GEN_FAILURE)); + } + HDC screen_dc = GetDC(nullptr); if (screen_dc == nullptr) { return BuildTextResponseBytes( @@ -979,6 +1288,14 @@ void SecureDesktopSharedCaptureThread( static_cast(request.width) * request.height * 3 / 2; std::vector nv12_frame(nv12_size); + ScopedDesktopHandle desktop; + if (!EnsureThreadInteractiveDesktopForStage(request.interactive_stage, + &desktop.handle)) { + LOG_ERROR("Secure shared capture desktop switch failed, error={}", + GetLastError()); + return; + } + HDC screen_dc = GetDC(nullptr); if (screen_dc == nullptr) { LOG_ERROR("Secure shared capture GetDC failed, error={}", GetLastError()); @@ -1224,17 +1541,21 @@ std::vector HandleSecureInputHelperCommand( bool is_down = false; uint32_t scan_code = 0; bool extended = false; + std::string interactive_stage; 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) { + &extended, &interactive_stage)) { + const InputInjectionResult inject_result = + InjectKeyboardInput(key_code, is_down, scan_code, extended, + interactive_stage); + if (!inject_result.ok) { LOG_WARN( - "Secure input helper SendInput failed for key_code={}, is_down={}, " - "scan_code={}, extended={}, err={}", - key_code, is_down, scan_code, extended, inject_result); - return BuildTextResponseBytes(BuildErrorJson( - "send_input_failed", static_cast(inject_result))); + "Secure input helper input failed for key_code={}, is_down={}, " + "scan_code={}, extended={}, error='{}', stage='{}', target='{}', " + "current='{}', code={}", + key_code, is_down, scan_code, extended, inject_result.error, + interactive_stage, inject_result.desktop.target_desktop, + inject_result.desktop.current_desktop, inject_result.error_code); + return BuildTextResponseBytes(BuildInputFailureJson(inject_result).dump()); } Json json; @@ -1244,21 +1565,24 @@ std::vector HandleSecureInputHelperCommand( json["is_down"] = is_down; json["scan_code"] = scan_code; json["extended"] = extended; + json["stage"] = interactive_stage; json["desktop"] = WideToUtf8(GetCurrentThreadDesktopNameW()); return BuildTextResponseBytes(json.dump()); } SecureMouseRequest mouse_request; if (ParseSecureInputMouseCommand(command, &mouse_request)) { - const int inject_result = InjectMouseInput(mouse_request); - if (inject_result != 0) { + const InputInjectionResult inject_result = InjectMouseInput(mouse_request); + if (!inject_result.ok) { LOG_WARN( - "Secure input helper SendInput failed for mouse x={}, y={}, " - "wheel={}, flag={}, err={}", + "Secure input helper input failed for mouse x={}, y={}, " + "wheel={}, flag={}, error='{}', stage='{}', target='{}', " + "current='{}', code={}", mouse_request.x, mouse_request.y, mouse_request.wheel, - mouse_request.flag, inject_result); - return BuildTextResponseBytes(BuildErrorJson( - "send_input_failed", static_cast(inject_result))); + mouse_request.flag, inject_result.error, + mouse_request.interactive_stage, inject_result.desktop.target_desktop, + inject_result.desktop.current_desktop, inject_result.error_code); + return BuildTextResponseBytes(BuildInputFailureJson(inject_result).dump()); } Json json; @@ -1268,6 +1592,7 @@ std::vector HandleSecureInputHelperCommand( json["y"] = mouse_request.y; json["wheel"] = mouse_request.wheel; json["flag"] = mouse_request.flag; + json["stage"] = mouse_request.interactive_stage; json["desktop"] = WideToUtf8(GetCurrentThreadDesktopNameW()); return BuildTextResponseBytes(json.dump()); } @@ -1394,6 +1719,8 @@ void PrintUsage() { } // namespace int main(int argc, char* argv[]) { + EnablePerMonitorDpiAwareness(); + InitializeHelperLogger(); bool run_helper = false; @@ -1447,24 +1774,10 @@ int main(int argc, char* argv[]) { expected_session_id, current_session_id); } - HDESK secure_desktop = nullptr; - if (!EnsureThreadDesktop(L"Winlogon", &secure_desktop)) { - LOG_ERROR( - "Failed to switch secure input helper to Winlogon desktop, error={}", - GetLastError()); - if (stop_event != nullptr) { - CloseHandle(stop_event); - } - return 1; - } - - LOG_INFO("Secure input helper desktop: '{}'", + LOG_INFO("Secure input helper initial desktop: '{}'", WideToUtf8(GetCurrentThreadDesktopNameW())); SecureInputHelperIpcServerLoop(stop_event, current_session_id); - if (secure_desktop != nullptr) { - CloseDesktop(secure_desktop); - } if (stop_event != nullptr) { CloseHandle(stop_event); } diff --git a/tests/windows_service_mouse_ipc_test.cpp b/tests/windows_service_mouse_ipc_test.cpp index 52e6333..163dc40 100644 --- a/tests/windows_service_mouse_ipc_test.cpp +++ b/tests/windows_service_mouse_ipc_test.cpp @@ -39,6 +39,16 @@ bool ExpectContains(const char* name, const std::string& value, return false; } +bool ExpectNotContains(const char* name, const std::string& value, + const std::string& unexpected) { + if (value.find(unexpected) == std::string::npos) { + return true; + } + + std::cerr << name << " contains unexpected text: " << unexpected << "\n"; + return false; +} + } // namespace int main() { @@ -50,13 +60,166 @@ int main() { const std::string service_host = ReadFile(repo_root / "src/service/windows/service_host.cpp"); + const std::string service_host_h = + ReadFile(repo_root / "src/service/windows/service_host.h"); + const std::string session_helper = + ReadFile(repo_root / "src/service/windows/session_helper_main.cpp"); + const std::string targets = + ReadFile(repo_root / "xmake/targets.lua"); + const std::string interactive_state = + ReadFile(repo_root / "src/service/windows/interactive_state.h"); + const std::string render_callback = + ReadFile(repo_root / "src/gui/render_callback.cpp"); + const std::string render = ReadFile(repo_root / "src/gui/render.cpp"); + const std::string screen_capturer_h = + ReadFile(repo_root / "src/screen_capturer/windows/screen_capturer_win.h"); + const std::string screen_capturer_cpp = + ReadFile(repo_root / "src/screen_capturer/windows/screen_capturer_win.cpp"); bool ok = true; ok &= ExpectContains("service_host.cpp", service_host, "ParseSecureDesktopMouseIpcCommand"); ok &= ExpectContains("service_host.cpp", service_host, "BuildSecureInputHelperMouseCommand"); + ok &= ExpectContains("targets.lua", targets, + "target(\"crossdesk_session_helper\")"); + ok &= ExpectContains("targets.lua", targets, + "add_files(\"scripts/windows/crossdesk.rc\")"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "EnablePerMonitorDpiAwareness"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "SetProcessDpiAwarenessContext(\n" + " DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2)"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "EnablePerMonitorDpiAwareness();\n\n" + " InitializeHelperLogger();"); + ok &= ExpectContains("service_host.cpp", service_host, + "const ULONGLONG deadline_tick = GetTickCount64() + timeout_ms"); + ok &= ExpectContains("service_host.cpp", service_host, + "while (GetTickCount64() <= deadline_tick)"); + ok &= ExpectNotContains("service_host.cpp", service_host, + "constexpr int kPipeConnectRetryCount = 3"); + ok &= ExpectContains("service_host.cpp", service_host, + "BuildSecureInputHelperKeyboardCommand("); + ok &= ExpectContains("service_host.cpp", service_host, + "const std::string& interactive_stage"); + ok &= ExpectContains("service_host.h", service_host_h, + "bool LaunchSecureInputHelper(DWORD session_id,\n" + " const std::string& interactive_stage)"); + ok &= ExpectContains("service_host.h", service_host_h, + "std::string secure_input_helper_interactive_stage_"); + ok &= ExpectContains("service_host.cpp", service_host, + "SecureInputHelperDesktopForStage"); + ok &= ExpectContains("service_host.cpp", service_host, + "return L\"winsta0\\\\Winlogon\""); + ok &= ExpectContains("service_host.cpp", service_host, + "return L\"winsta0\\\\default\""); + ok &= ExpectContains("service_host.cpp", service_host, + "secure_input_helper_interactive_stage_ == interactive_stage"); + ok &= ExpectContains("service_host.cpp", service_host, + "secure_input_helper_interactive_stage_ = interactive_stage"); + ok &= ExpectContains("service_host.cpp", service_host, + "secure_input_helper_interactive_stage_.clear()"); + ok &= ExpectContains("service_host.cpp", service_host, + "LaunchSecureInputHelper(target_session_id, interactive_stage)"); + ok &= ExpectContains("service_host.cpp", service_host, + "\\\"secure_input_helper_stage\\\":\\\""); + ok &= ExpectContains("service_host.cpp", service_host, + "session_helper_report_interactive_stage_"); ok &= ExpectContains("service_host.cpp", service_host, "return SendSecureDesktopMouseInput"); + ok &= ExpectContains("render.cpp", render, + "constexpr DWORD kWindowsServiceQueryTimeoutMs = 500"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 500"); + ok &= ExpectContains("render.cpp", render, + "IsTransientWindowsServiceStatusError(status.error)"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "IsTransientWindowsServiceStatusError(status.error)"); + ok &= ExpectContains("render.cpp", render, + "Local Windows service temporarily unavailable"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "Windows capturer secure desktop service temporarily unavailable"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "Windows capturer secure desktop transient frame query failed"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "if (transient_error) {\n" + " LOG_INFO("); + ok &= ExpectContains("render_callback.cpp", render_callback, + "IsTransientSecureDesktopInputFailure"); + ok &= ExpectContains("render_callback.cpp", render_callback, + "Secure desktop keyboard injection transient failure"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "MOUSEEVENTF_VIRTUALDESK"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "std::vector inputs"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "SendInput(static_cast(inputs.size())"); + ok &= ExpectNotContains("session_helper_main.cpp", session_helper, + "SetCursorPos(request.x, request.y)"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "NormalizeAbsoluteMouseCoordinate"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "EnsureThreadInteractiveDesktop"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "OpenInputDesktop"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "DesktopNameForInteractiveStage"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "interactive_stage == \"credential-ui\""); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "return L\"Winlogon\""); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "interactive_stage == \"lock-screen\""); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "return L\"Default\""); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "EnsureThreadInteractiveDesktopForStage"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "switch_interactive_desktop_failed"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "Json BuildInputFailureJson"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "json[\"target_desktop\"]"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "json[\"current_desktop\"]"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "json[\"stage\"]"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "ParseSecureInputKeyboardCommand(command, &key_code, &is_down, &scan_code,\n" + " &extended, &interactive_stage)"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "InjectKeyboardInput(key_code, is_down, scan_code, extended,\n" + " interactive_stage)"); + ok &= ExpectContains("session_helper_main.cpp", session_helper, + "InjectMouseInput(mouse_request)"); + ok &= ExpectNotContains("session_helper_main.cpp", session_helper, + "EnsureThreadDesktop(L\"Winlogon\", &secure_desktop)"); + ok &= ExpectContains("service_host.cpp", service_host, + "winsta0\\\\default"); + ok &= ExpectNotContains("service_host.cpp", service_host, + "startup_info.lpDesktop = const_cast(L\"winsta0\\\\Winlogon\")"); + ok &= ExpectContains("interactive_state.h", interactive_state, + "interactive_stage == \"lock-screen\""); + ok &= ExpectContains("render_callback.cpp", render_callback, + "RemoteAction remote_action{};"); + ok &= ExpectContains("render.cpp", render, + "previous_secure_desktop_interaction"); + ok &= ExpectNotContains( + "render_callback.cpp", render_callback, + "render->local_service_available_ &&\n" + " IsSecureDesktopInteractionRequired(render->local_interactive_stage_)"); + ok &= ExpectContains("screen_capturer_win.h", screen_capturer_h, + "std::string secure_shared_stage_;"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "const std::string& stage"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "secure_shared_stage_ == stage"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "secure_shared_stage_ = stage"); + ok &= ExpectContains("screen_capturer_win.cpp", screen_capturer_cpp, + "secure_shared_stage_.clear()"); return ok ? 0 : 1; } diff --git a/xmake/targets.lua b/xmake/targets.lua index a1afced..985e3c3 100644 --- a/xmake/targets.lua +++ b/xmake/targets.lua @@ -217,6 +217,7 @@ function setup_targets() add_deps("rd_log", "path_manager") add_links("Advapi32", "User32", "Wtsapi32", "Gdi32") add_files("src/service/windows/session_helper_main.cpp") + add_files("scripts/windows/crossdesk.rc") add_includedirs("src/service/windows", {public = true}) end