From 5a7e816fb7aab6efbc5ab2c458ffc6c35c4ddd5e Mon Sep 17 00:00:00 2001 From: dijunkun Date: Fri, 9 Oct 2026 16:02:27 +0800 Subject: [PATCH] [fix] restore remote input for elevated Windows apps --- .../features/input/keyboard_controller.cpp | 5 ++- .../src/gui/runtime/peer_data_callbacks.cpp | 5 ++- .../service/desktop_transition_policy.h | 7 +++ .../platform/windows/service/service_host.cpp | 44 +++++++++++++++---- .../platform/windows/service/service_host.h | 3 ++ 5 files changed, 53 insertions(+), 11 deletions(-) diff --git a/apps/desktop/src/gui/features/input/keyboard_controller.cpp b/apps/desktop/src/gui/features/input/keyboard_controller.cpp index 435ed306..61829209 100644 --- a/apps/desktop/src/gui/features/input/keyboard_controller.cpp +++ b/apps/desktop/src/gui/features/input/keyboard_controller.cpp @@ -238,8 +238,11 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down, SetLastError(ERROR_SUCCESS); const bool sent = owner_.devices_.SendKeyboardCommand( key_code, is_down, scan_code, extended); + const DWORD error = GetLastError(); + const bool blocked = + error == ERROR_ACCESS_DENIED || error == ERROR_GEN_FAILURE; return DesktopInputResult{ - sent, IsDesktopTransitionInputError(GetLastError())}; + sent, IsDesktopTransitionInputError(error) || blocked, blocked}; }, [&]() -> DesktopInputResult { const std::string response = SendCrossDeskSecureDesktopKeyInput( diff --git a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp index ef0c5098..4c670220 100644 --- a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp +++ b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp @@ -404,8 +404,11 @@ void PeerEventHandler::OnReceiveDataBuffer( SetLastError(ERROR_SUCCESS); const bool sent = runtime->devices_.SendMouseCommand( remote_action, runtime->selected_display_); + const DWORD error = GetLastError(); + const bool blocked = + error == ERROR_ACCESS_DENIED || error == ERROR_GEN_FAILURE; return DesktopInputResult{ - sent, IsDesktopTransitionInputError(GetLastError())}; + sent, IsDesktopTransitionInputError(error) || blocked, blocked}; }, [&]() -> DesktopInputResult { int absolute_x = 0; diff --git a/apps/desktop/src/platform/windows/service/desktop_transition_policy.h b/apps/desktop/src/platform/windows/service/desktop_transition_policy.h index 86ec608f..31bb3e48 100644 --- a/apps/desktop/src/platform/windows/service/desktop_transition_policy.h +++ b/apps/desktop/src/platform/windows/service/desktop_transition_policy.h @@ -39,6 +39,9 @@ struct DesktopInputResult { // A pipe timeout may have happened after a click/key was applied: never // replay it. bool not_injected = false; + // The ordinary desktop injector rejected its single input event (for + // example UIPI). The authorized service can retry even on the same desktop. + bool retry_with_service = false; }; template @@ -48,6 +51,10 @@ bool DispatchDesktopInput(ProbeUserDesktop probe_user, SendUser send_user, const auto result = user ? send_user() : send_secure(); if (result.delivered) return true; if (!result.not_injected) return false; + if (user && result.retry_with_service) { + // Do not replay a failed/ambiguous service request through another path. + return send_secure().delivered; + } const bool current_user = probe_user(); if (current_user == user) return false; // Handle a switch between the probe and injection exactly once. Ordering is diff --git a/apps/desktop/src/platform/windows/service/service_host.cpp b/apps/desktop/src/platform/windows/service/service_host.cpp index 6e1238f2..276afcc5 100644 --- a/apps/desktop/src/platform/windows/service/service_host.cpp +++ b/apps/desktop/src/platform/windows/service/service_host.cpp @@ -38,6 +38,7 @@ constexpr wchar_t kCrossDeskClientProcessName[] = L"crossdesk.exe"; constexpr DWORD kCrossDeskClientMonitorIntervalMs = 1000; constexpr ULONGLONG kCrossDeskClientMonitorStartupGraceMs = 5000; constexpr ULONGLONG kSasSecureDesktopGraceMs = 15000; +constexpr ULONGLONG kUserDesktopInputIdleMs = 5000; using SendSasFunction = VOID(WINAPI*)(BOOL); @@ -1057,6 +1058,7 @@ int CrossDeskServiceHost::InitializeRuntime() { session_helper_report_state_age_ms_ = 0; session_helper_report_uptime_ms_ = 0; secure_input_helper_started_at_tick_ = 0; + user_desktop_input_until_tick_ = 0; sas_secure_desktop_until_tick_ = 0; session_helper_process_handle_ = nullptr; session_helper_stop_event_ = nullptr; @@ -1460,6 +1462,7 @@ void CrossDeskServiceHost::RefreshSessionState() { ResetSessionHelperReportedStateLocked("session_changed", 0); sas_secure_desktop_until_tick_ = 0; sas_secure_desktop_seen_ = false; + user_desktop_input_until_tick_ = 0; } process_session_id_ = process_session; logon_ui_visible_ = logon_ui; @@ -1574,7 +1577,8 @@ bool CrossDeskServiceHost::ShouldKeepSecureInputHelperLocked( return false; } - return HasSecureInputUiLocked() || GetEffectiveSessionLockedLocked(); + return HasSecureInputUiLocked() || GetEffectiveSessionLockedLocked() || + GetTickCount64() < user_desktop_input_until_tick_; } std::string CrossDeskServiceHost::ResolveInteractiveStageLocked() const { @@ -1622,6 +1626,10 @@ std::string CrossDeskServiceHost::ResolveInteractiveDesktopLocked( return "Default"; } + if (interactive_stage == "user-desktop") { + return "Default"; + } + if (input_desktop_available_ && !input_desktop_name_.empty() && (interactive_stage == "credential-ui" || consent_ui_visible_)) { return input_desktop_name_; @@ -2174,8 +2182,12 @@ void CrossDeskServiceHost::RecordSessionEvent(DWORD event_type, ResetSessionHelperReportedStateLocked("session_changed", 0); sas_secure_desktop_until_tick_ = 0; sas_secure_desktop_seen_ = false; + user_desktop_input_until_tick_ = 0; } if (session_id == active_session_id_) { + if (event_type == WTS_SESSION_LOGOFF) { + user_desktop_input_until_tick_ = 0; + } // Do not let an older helper sample override a session notification. ResetSessionHelperReportedStateLocked("session_event", 0); last_session_transition_tick_ = GetTickCount64(); @@ -2501,20 +2513,28 @@ std::string CrossDeskServiceHost::ResolveSecureInputTarget( { std::lock_guard lock(state_mutex_); target.session_id = active_session_id_; + if (target.session_id == 0xFFFFFFFF) { + return BuildErrorJson("no_active_console_session"); + } + if (target.session_id != client_session_id || + client_session_id != WTSGetActiveConsoleSessionId()) { + return BuildErrorJson("service_session_mismatch"); + } target.stage = ResolveInteractiveStageLocked(); target.desktop = ResolveInteractiveDesktopLocked(target.stage); helper_running = secure_input_helper_running_ && secure_input_helper_session_id_ == target.session_id; can_inject = GetEffectiveSessionLockedLocked() || HasSecureInputUiLocked(); + if (target.stage == "user-desktop") { + // Ordinary GUI input can be blocked by an elevated foreground window. + // Only a Windows-validated client in the active console session may + // start/retain this helper; do not misreport the desktop as UAC/locked. + user_desktop_input_until_tick_ = + GetTickCount64() + kUserDesktopInputIdleMs; + can_inject = true; + } } - if (target.session_id == 0xFFFFFFFF) { - return BuildErrorJson("no_active_console_session"); - } - if (target.session_id != client_session_id || - client_session_id != WTSGetActiveConsoleSessionId()) { - return BuildErrorJson("service_session_mismatch"); - } if (!can_inject) { WakeSessionState(); return BuildErrorJson("secure_input_not_active"); @@ -2799,8 +2819,14 @@ std::string QueryDesktopInput(const std::string& command, DWORD timeout_ms) { const std::string error = json.value("error", ""); // Retry only explicit pre-injection rejections during helper startup. // Never replay an uncertain timeout or a partially injected mouse action. - if (!IsDesktopInputSetupPending(error) || IsCurrentSessionUserDesktopActive()) + // The SYSTEM helper may now be starting for an elevated application on + // Default too. Old services still reject that with secure_input_not_active; + // preserve their ordinary-desktop return instead of retrying indefinitely. + if (!IsDesktopInputSetupPending(error) || + (error == "secure_input_not_active" && + IsCurrentSessionUserDesktopActive())) { return response; + } const auto remaining = pipe_deadline_detail::Remaining(deadline); if (remaining == 0) return response; Sleep((std::min)(DWORD{10}, remaining)); diff --git a/apps/desktop/src/platform/windows/service/service_host.h b/apps/desktop/src/platform/windows/service/service_host.h index 3f549546..01c78a20 100644 --- a/apps/desktop/src/platform/windows/service/service_host.h +++ b/apps/desktop/src/platform/windows/service/service_host.h @@ -137,6 +137,9 @@ class CrossDeskServiceHost { ULONGLONG session_helper_report_state_age_ms_ = 0; ULONGLONG session_helper_report_uptime_ms_ = 0; ULONGLONG secure_input_helper_started_at_tick_ = 0; + // Short idle grace after a validated user-desktop input request. This keeps + // the SYSTEM helper available for elevated windows such as Task Manager. + ULONGLONG user_desktop_input_until_tick_ = 0; ULONGLONG sas_secure_desktop_until_tick_ = 0; bool session_locked_ = false; bool logon_ui_visible_ = false;