[fix] route unelevated Windows input through service

This commit is contained in:
kunkundi
2026-10-09 16:33:51 +08:00
parent 5a7e816fb7
commit 055d8424a6
8 changed files with 76 additions and 12 deletions
@@ -19,8 +19,10 @@
#include "runtime/gui_runtime.h" #include "runtime/gui_runtime.h"
#include "speaker_capturer_factory.h" #include "speaker_capturer_factory.h"
#ifdef _WIN32 #ifdef _WIN32
#include <nlohmann/json.hpp>
#include "platform/windows/screen_capturer/screen_capturer_win.h" #include "platform/windows/screen_capturer/screen_capturer_win.h"
#include "platform/windows/input/mouse/mouse_controller.h" #include "platform/windows/input/mouse/mouse_controller.h"
#include "service_host.h"
#endif #endif
namespace crossdesk { namespace crossdesk {
@@ -327,6 +329,37 @@ int SessionDeviceManager::StopScreenCapturer() {
void SessionDeviceManager::ReleaseRemoteMouseButtons() { void SessionDeviceManager::ReleaseRemoteMouseButtons() {
if (auto mouse = dynamic_cast<PlatformMouseController*>(mouse_controller_)) if (auto mouse = dynamic_cast<PlatformMouseController*>(mouse_controller_))
mouse->ReleasePressedButtons(); 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 #endif
@@ -412,6 +445,9 @@ int SessionDeviceManager::StartMouseController() {
} }
int SessionDeviceManager::StopMouseController() { int SessionDeviceManager::StopMouseController() {
#ifdef _WIN32
ReleaseRemoteMouseButtons();
#endif
if (mouse_controller_) { if (mouse_controller_) {
mouse_controller_->Destroy(); mouse_controller_->Destroy();
delete mouse_controller_; delete mouse_controller_;
@@ -718,11 +754,7 @@ std::vector<HostDisplay> SessionDeviceManager::host_display_list() const {
void SessionDeviceManager::DestroyDevices() { void SessionDeviceManager::DestroyDevices() {
speaker_capture_.Shutdown(); speaker_capture_.Shutdown();
if (mouse_controller_) { StopMouseController();
mouse_controller_->Destroy();
delete mouse_controller_;
mouse_controller_ = nullptr;
}
if (screen_capturer_) { if (screen_capturer_) {
screen_capturer_->Destroy(); screen_capturer_->Destroy();
delete screen_capturer_; delete screen_capturer_;
@@ -54,6 +54,7 @@ public:
int StopScreenCapturer(); int StopScreenCapturer();
#ifdef _WIN32 #ifdef _WIN32
void ReleaseRemoteMouseButtons(); void ReleaseRemoteMouseButtons();
std::string SendServiceMouseCommand(int x, int y, int wheel, int flag);
#endif #endif
// Requests are asynchronous; Stop drains/revokes audio delivery before // Requests are asynchronous; Stop drains/revokes audio delivery before
// returning so the connection peer can be destroyed safely. // returning so the connection peer can be destroyed safely.
@@ -107,6 +108,9 @@ private:
CapturedKeyboardQueue captured_keyboard_inputs_; CapturedKeyboardQueue captured_keyboard_inputs_;
#ifdef _WIN32 #ifdef _WIN32
std::chrono::steady_clock::time_point next_keyboard_capture_retry_{}; 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 #endif
std::chrono::steady_clock::time_point last_frame_time_{}; std::chrono::steady_clock::time_point last_frame_time_{};
std::chrono::steady_clock::time_point next_frame_deadline_{}; std::chrono::steady_clock::time_point next_frame_deadline_{};
@@ -19,6 +19,7 @@
#include "desktop_transition_policy.h" #include "desktop_transition_policy.h"
#include "interactive_desktop.h" #include "interactive_desktop.h"
#include "service_host.h" #include "service_host.h"
#include "unattended_config.h"
#endif #endif
namespace crossdesk { namespace crossdesk {
@@ -229,10 +230,13 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down,
owner_.privacy_.SuspendForDesktop(); owner_.privacy_.SuspendForDesktop();
} }
if (owner_.is_server_mode_ || !owner_.WindowsInputStage().empty()) { if (owner_.is_server_mode_ || !owner_.WindowsInputStage().empty()) {
static const bool process_elevated = IsAdministratorProcess();
return DispatchDesktopInput( return DispatchDesktopInput(
[&] { [&] {
return PreferUserDesktopInput(IsCurrentSessionUserDesktopActive(), return PreferUserDesktopInput(IsCurrentSessionUserDesktopActive(),
owner_.windows_consent_ui_.load()); owner_.windows_consent_ui_.load(),
owner_.local_service_available_.load(),
process_elevated);
}, },
[&] { [&] {
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
@@ -29,6 +29,7 @@
#include "desktop_transition_policy.h" #include "desktop_transition_policy.h"
#include "interactive_desktop.h" #include "interactive_desktop.h"
#include "service_host.h" #include "service_host.h"
#include "unattended_config.h"
#endif #endif
namespace crossdesk { namespace crossdesk {
@@ -394,11 +395,14 @@ void PeerEventHandler::OnReceiveDataBuffer(
} }
#if _WIN32 #if _WIN32
if (runtime->is_server_mode_ && remote_action.type == ControlType::mouse) { if (runtime->is_server_mode_ && remote_action.type == ControlType::mouse) {
static const bool process_elevated = IsAdministratorProcess();
std::string response; std::string response;
const bool delivered = DispatchDesktopInput( const bool delivered = DispatchDesktopInput(
[&] { [&] {
return PreferUserDesktopInput(IsCurrentSessionUserDesktopActive(), return PreferUserDesktopInput(
runtime->windows_consent_ui_.load()); IsCurrentSessionUserDesktopActive(),
runtime->windows_consent_ui_.load(),
runtime->local_service_available_.load(), process_elevated);
}, },
[&] { [&] {
SetLastError(ERROR_SUCCESS); SetLastError(ERROR_SUCCESS);
@@ -425,9 +429,9 @@ void PeerEventHandler::OnReceiveDataBuffer(
return {}; return {};
} }
response = SendCrossDeskSecureDesktopMouseInput( response = runtime->devices_.SendServiceMouseCommand(
absolute_x, absolute_y, remote_action.m.s, absolute_x, absolute_y, remote_action.m.s,
static_cast<int>(remote_action.m.flag), 1000); static_cast<int>(remote_action.m.flag));
auto json = nlohmann::json::parse(response, nullptr, false); auto json = nlohmann::json::parse(response, nullptr, false);
const bool sent = json.is_object() && json.value("ok", false); const bool sent = json.is_object() && json.value("ok", false);
const std::string error = const std::string error =
+1 -1
View File
@@ -94,7 +94,7 @@ struct PlatformIntegrationState {
std::atomic<bool> windows_consent_ui_{false}; std::atomic<bool> windows_consent_ui_{false};
std::atomic<bool> pending_windows_service_sas_{false}; std::atomic<bool> pending_windows_service_sas_{false};
bool local_service_status_received_ = false; bool local_service_status_received_ = false;
bool local_service_available_ = false; std::atomic<bool> local_service_available_{false};
std::string local_interactive_stage_; std::string local_interactive_stage_;
uint32_t last_local_secure_input_block_log_tick_ = 0; uint32_t last_local_secure_input_block_log_tick_ = 0;
uint32_t last_windows_service_status_tick_ = 0; uint32_t last_windows_service_status_tick_ = 0;
@@ -17,6 +17,7 @@
#include "desktop_transition_policy.h" #include "desktop_transition_policy.h"
#include "interactive_state.h" #include "interactive_state.h"
#include "service_host.h" #include "service_host.h"
#include "unattended_config.h"
#endif #endif
namespace crossdesk { namespace crossdesk {
@@ -205,7 +206,12 @@ void GuiRuntime::HandleWindowsServiceIntegration() {
status.interactive_stage == "user-desktop"; status.interactive_stage == "user-desktop";
local_service_status_received_ = local_service_status_received_ =
status_ok || previous_secure_desktop_interaction; 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 (status.available) {
if (keep_optimistic_secure_desktop) { if (keep_optimistic_secure_desktop) {
local_interactive_stage_ = "secure-desktop"; local_interactive_stage_ = "secure-desktop";
@@ -19,6 +19,18 @@ inline bool PreferUserDesktopInput(bool user_desktop_active,
return user_desktop_active && !consent_ui_visible; 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) { inline bool IsDesktopInputSetupPending(const std::string& error) {
// These failures explicitly precede injection; timeouts remain ambiguous. // These failures explicitly precede injection; timeouts remain ambiguous.
return error == "secure_input_helper_not_ready" || return error == "secure_input_helper_not_ready" ||
@@ -1250,6 +1250,7 @@ LONG NormalizeAbsoluteMouseCoordinate(int value, int origin, int size) {
INPUT BuildAbsoluteMouseMoveInput(int x, int y) { INPUT BuildAbsoluteMouseMoveInput(int x, int y) {
INPUT input = {0}; INPUT input = {0};
input.type = INPUT_MOUSE; input.type = INPUT_MOUSE;
input.mi.dwExtraInfo = crossdesk::kInjectedMouseInputMarker;
const int virtual_left = GetSystemMetrics(SM_XVIRTUALSCREEN); const int virtual_left = GetSystemMetrics(SM_XVIRTUALSCREEN);
const int virtual_top = GetSystemMetrics(SM_YVIRTUALSCREEN); const int virtual_top = GetSystemMetrics(SM_YVIRTUALSCREEN);
@@ -1283,6 +1284,7 @@ InputInjectionResult InjectMouseInput(const SecureMouseRequest& request) {
INPUT action_input = {0}; INPUT action_input = {0};
action_input.type = INPUT_MOUSE; action_input.type = INPUT_MOUSE;
action_input.mi.dwExtraInfo = crossdesk::kInjectedMouseInputMarker;
switch (request.flag) { switch (request.flag) {
case 0: case 0:
break; break;