diff --git a/apps/desktop/src/gui/features/devices/session_device_manager.cpp b/apps/desktop/src/gui/features/devices/session_device_manager.cpp index 2195611f..e6385c9a 100644 --- a/apps/desktop/src/gui/features/devices/session_device_manager.cpp +++ b/apps/desktop/src/gui/features/devices/session_device_manager.cpp @@ -19,8 +19,10 @@ #include "runtime/gui_runtime.h" #include "speaker_capturer_factory.h" #ifdef _WIN32 +#include #include "platform/windows/screen_capturer/screen_capturer_win.h" #include "platform/windows/input/mouse/mouse_controller.h" +#include "service_host.h" #endif namespace crossdesk { @@ -327,6 +329,37 @@ int SessionDeviceManager::StopScreenCapturer() { void SessionDeviceManager::ReleaseRemoteMouseButtons() { if (auto mouse = dynamic_cast(mouse_controller_)) mouse->ReleasePressedButtons(); + std::lock_guard lock(service_mouse_mutex_); + for (int button = 0; button < 3; ++button) { + const unsigned bit = 1u << button; + if (!(service_mouse_buttons_ & bit)) continue; + const auto response = SendCrossDeskSecureDesktopMouseInput( + service_mouse_x_, service_mouse_y_, 0, (button + 1) * 2, 1000); + const auto json = nlohmann::json::parse(response, nullptr, false); + if (json.is_object() && json.value("ok", false)) { + service_mouse_buttons_ &= ~bit; + } else { + LOG_WARN("Service mouse button release failed: button={}, response={}", + button, response); + } + } +} + +std::string SessionDeviceManager::SendServiceMouseCommand(int x, int y, + int wheel, int flag) { + std::lock_guard lock(service_mouse_mutex_); + service_mouse_x_ = x; + service_mouse_y_ = y; + const unsigned bit = flag >= 1 && flag <= 6 ? 1u << ((flag - 1) / 2) : 0; + // A timeout can follow successful injection. Keep downs for disconnect + // cleanup even if the reply was lost; only clear a confirmed release. + if (flag % 2 == 1) service_mouse_buttons_ |= bit; + const auto response = SendCrossDeskSecureDesktopMouseInput(x, y, wheel, flag, + 1000); + const auto json = nlohmann::json::parse(response, nullptr, false); + if (flag % 2 == 0 && json.is_object() && json.value("ok", false)) + service_mouse_buttons_ &= ~bit; + return response; } #endif @@ -412,6 +445,9 @@ int SessionDeviceManager::StartMouseController() { } int SessionDeviceManager::StopMouseController() { +#ifdef _WIN32 + ReleaseRemoteMouseButtons(); +#endif if (mouse_controller_) { mouse_controller_->Destroy(); delete mouse_controller_; @@ -718,11 +754,7 @@ std::vector SessionDeviceManager::host_display_list() const { void SessionDeviceManager::DestroyDevices() { speaker_capture_.Shutdown(); - if (mouse_controller_) { - mouse_controller_->Destroy(); - delete mouse_controller_; - mouse_controller_ = nullptr; - } + StopMouseController(); if (screen_capturer_) { screen_capturer_->Destroy(); delete screen_capturer_; diff --git a/apps/desktop/src/gui/features/devices/session_device_manager.h b/apps/desktop/src/gui/features/devices/session_device_manager.h index def0dd11..640afa7a 100644 --- a/apps/desktop/src/gui/features/devices/session_device_manager.h +++ b/apps/desktop/src/gui/features/devices/session_device_manager.h @@ -54,6 +54,7 @@ public: int StopScreenCapturer(); #ifdef _WIN32 void ReleaseRemoteMouseButtons(); + std::string SendServiceMouseCommand(int x, int y, int wheel, int flag); #endif // Requests are asynchronous; Stop drains/revokes audio delivery before // returning so the connection peer can be destroyed safely. @@ -107,6 +108,9 @@ private: CapturedKeyboardQueue captured_keyboard_inputs_; #ifdef _WIN32 std::chrono::steady_clock::time_point next_keyboard_capture_retry_{}; + std::mutex service_mouse_mutex_; + unsigned service_mouse_buttons_ = 0; + int service_mouse_x_ = 0, service_mouse_y_ = 0; #endif std::chrono::steady_clock::time_point last_frame_time_{}; std::chrono::steady_clock::time_point next_frame_deadline_{}; diff --git a/apps/desktop/src/gui/features/input/keyboard_controller.cpp b/apps/desktop/src/gui/features/input/keyboard_controller.cpp index 61829209..41be9e39 100644 --- a/apps/desktop/src/gui/features/input/keyboard_controller.cpp +++ b/apps/desktop/src/gui/features/input/keyboard_controller.cpp @@ -19,6 +19,7 @@ #include "desktop_transition_policy.h" #include "interactive_desktop.h" #include "service_host.h" +#include "unattended_config.h" #endif namespace crossdesk { @@ -229,10 +230,13 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down, owner_.privacy_.SuspendForDesktop(); } if (owner_.is_server_mode_ || !owner_.WindowsInputStage().empty()) { + static const bool process_elevated = IsAdministratorProcess(); return DispatchDesktopInput( [&] { return PreferUserDesktopInput(IsCurrentSessionUserDesktopActive(), - owner_.windows_consent_ui_.load()); + owner_.windows_consent_ui_.load(), + owner_.local_service_available_.load(), + process_elevated); }, [&] { SetLastError(ERROR_SUCCESS); diff --git a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp index 4c670220..e2d5a120 100644 --- a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp +++ b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp @@ -29,6 +29,7 @@ #include "desktop_transition_policy.h" #include "interactive_desktop.h" #include "service_host.h" +#include "unattended_config.h" #endif namespace crossdesk { @@ -394,11 +395,14 @@ void PeerEventHandler::OnReceiveDataBuffer( } #if _WIN32 if (runtime->is_server_mode_ && remote_action.type == ControlType::mouse) { + static const bool process_elevated = IsAdministratorProcess(); std::string response; const bool delivered = DispatchDesktopInput( [&] { - return PreferUserDesktopInput(IsCurrentSessionUserDesktopActive(), - runtime->windows_consent_ui_.load()); + return PreferUserDesktopInput( + IsCurrentSessionUserDesktopActive(), + runtime->windows_consent_ui_.load(), + runtime->local_service_available_.load(), process_elevated); }, [&] { SetLastError(ERROR_SUCCESS); @@ -425,9 +429,9 @@ void PeerEventHandler::OnReceiveDataBuffer( return {}; } - response = SendCrossDeskSecureDesktopMouseInput( + response = runtime->devices_.SendServiceMouseCommand( absolute_x, absolute_y, remote_action.m.s, - static_cast(remote_action.m.flag), 1000); + static_cast(remote_action.m.flag)); auto json = nlohmann::json::parse(response, nullptr, false); const bool sent = json.is_object() && json.value("ok", false); const std::string error = diff --git a/apps/desktop/src/gui/runtime/runtime_state.h b/apps/desktop/src/gui/runtime/runtime_state.h index bdeccaba..cc37c37b 100644 --- a/apps/desktop/src/gui/runtime/runtime_state.h +++ b/apps/desktop/src/gui/runtime/runtime_state.h @@ -94,7 +94,7 @@ struct PlatformIntegrationState { std::atomic windows_consent_ui_{false}; std::atomic pending_windows_service_sas_{false}; bool local_service_status_received_ = false; - bool local_service_available_ = false; + std::atomic local_service_available_{false}; std::string local_interactive_stage_; uint32_t last_local_secure_input_block_log_tick_ = 0; uint32_t last_windows_service_status_tick_ = 0; 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 0d780efa..4bee9fab 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 @@ -17,6 +17,7 @@ #include "desktop_transition_policy.h" #include "interactive_state.h" #include "service_host.h" +#include "unattended_config.h" #endif namespace crossdesk { @@ -205,7 +206,12 @@ void GuiRuntime::HandleWindowsServiceIntegration() { status.interactive_stage == "user-desktop"; local_service_status_received_ = status_ok || previous_secure_desktop_interaction; - local_service_available_ = status.available; + const bool service_was_available = + local_service_available_.exchange(status.available); + if (status.available && !service_was_available && !IsAdministratorProcess()) { + LOG_INFO("Unelevated host input routed through Windows service, including " + "elevated applications on the user desktop"); + } if (status.available) { if (keep_optimistic_secure_desktop) { local_interactive_stage_ = "secure-desktop"; 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 31bb3e48..2d1b86c2 100644 --- a/apps/desktop/src/platform/windows/service/desktop_transition_policy.h +++ b/apps/desktop/src/platform/windows/service/desktop_transition_policy.h @@ -19,6 +19,18 @@ inline bool PreferUserDesktopInput(bool user_desktop_active, return user_desktop_active && !consent_ui_visible; } +inline bool PreferUserDesktopInput(bool user_desktop_active, + bool consent_ui_visible, + bool service_available, + bool process_elevated) { + // An elevated application still lives on Default. SendInput only reports + // insertion into the input stream, not delivery to that application's UI. + // Use the authorized service proactively for an unelevated host, including + // clicks on an elevated window that has not become the foreground window. + return PreferUserDesktopInput(user_desktop_active, consent_ui_visible) && + (process_elevated || !service_available); +} + inline bool IsDesktopInputSetupPending(const std::string& error) { // These failures explicitly precede injection; timeouts remain ambiguous. return error == "secure_input_helper_not_ready" || 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 a0995602..f577cd1c 100644 --- a/apps/desktop/src/platform/windows/service/session_helper_main.cpp +++ b/apps/desktop/src/platform/windows/service/session_helper_main.cpp @@ -1250,6 +1250,7 @@ LONG NormalizeAbsoluteMouseCoordinate(int value, int origin, int size) { INPUT BuildAbsoluteMouseMoveInput(int x, int y) { INPUT input = {0}; input.type = INPUT_MOUSE; + input.mi.dwExtraInfo = crossdesk::kInjectedMouseInputMarker; const int virtual_left = GetSystemMetrics(SM_XVIRTUALSCREEN); const int virtual_top = GetSystemMetrics(SM_YVIRTUALSCREEN); @@ -1283,6 +1284,7 @@ InputInjectionResult InjectMouseInput(const SecureMouseRequest& request) { INPUT action_input = {0}; action_input.type = INPUT_MOUSE; + action_input.mi.dwExtraInfo = crossdesk::kInjectedMouseInputMarker; switch (request.flag) { case 0: break;