mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-27 08:12:18 +08:00
[fix] prevent remote input from blocking transport callbacks
This commit is contained in:
@@ -190,20 +190,13 @@ void GuiRuntime::HandleServerControllerDisconnected(
|
||||
const std::string& remote_id, const char* reason) {
|
||||
transfers_.Cancel(peer_events_.get(), remote_id);
|
||||
preview_subscriptions_.Remove(remote_id);
|
||||
keyboard_.ReleaseRemotePressedKeys(remote_id, reason);
|
||||
#ifdef _WIN32
|
||||
devices_.ReleaseRemoteMouseButtons();
|
||||
#endif
|
||||
{
|
||||
std::lock_guard lock(remote_pointer_input_mutex_);
|
||||
last_remote_pointer_input_time_.erase(remote_id);
|
||||
}
|
||||
|
||||
bool has_connected_controller = false;
|
||||
std::string remaining_controller_id;
|
||||
{
|
||||
std::unique_lock lock(connection_status_mutex_);
|
||||
connection_status_.erase(remote_id);
|
||||
remote_input_.Disconnect(remote_id);
|
||||
devices_.OnControllerConnectionsChanged();
|
||||
connection_host_names_.erase(remote_id);
|
||||
privacy_sessions_.Disconnected(remote_id);
|
||||
@@ -252,7 +245,6 @@ void GuiRuntime::HandleServerControllerDisconnected(
|
||||
start_keyboard_capturer_ = false;
|
||||
remote_client_id_.clear();
|
||||
audio_capture_ = false;
|
||||
devices_.ResetToInitialDisplay();
|
||||
}
|
||||
|
||||
int GuiRuntime::RequestSingleDevicePresence(const std::string& remote_id,
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
#include "localization.h"
|
||||
#include "platform/video_renderer.h"
|
||||
#include "rd_log.h"
|
||||
#ifdef _WIN32
|
||||
#include "platform/windows/input/windows_input_injector.h"
|
||||
#endif
|
||||
#if defined(__linux__) && !defined(__OHOS__)
|
||||
#include "platform/linux/headless/headless_console.h"
|
||||
#endif
|
||||
@@ -25,9 +28,43 @@ namespace crossdesk {
|
||||
|
||||
GuiRuntime::GuiRuntime()
|
||||
: clipboard_(*this), devices_(*this), transfers_(*this), settings_(*this),
|
||||
keyboard_(*this) {}
|
||||
keyboard_(*this),
|
||||
remote_input_(
|
||||
[this](const std::string& id, const RemoteAction& action) {
|
||||
#ifdef _WIN32
|
||||
ScopedUserDesktopInputThread input_thread;
|
||||
#endif
|
||||
return ApplyRemoteInput(id, action);
|
||||
},
|
||||
[this](const std::string& id) {
|
||||
#ifdef _WIN32
|
||||
ScopedUserDesktopInputThread input_thread;
|
||||
#endif
|
||||
const bool keys_released =
|
||||
keyboard_.ReleaseRemotePressedKeys(id, "input_queue_reset");
|
||||
const bool mouse_released = devices_.ReleaseRemoteMouseButtons(id);
|
||||
std::lock_guard lock(remote_pointer_input_mutex_);
|
||||
last_remote_pointer_input_time_.erase(id);
|
||||
return keys_released && mouse_released ? InputOutcome::applied
|
||||
: InputOutcome::failed;
|
||||
},
|
||||
[](const std::string& id, int64_t queued_ms, int64_t execution_ms,
|
||||
InputOutcome outcome) {
|
||||
LOG_WARN("Remote input result: peer={} queue_ms={} execution_ms={} "
|
||||
"result={}",
|
||||
id, queued_ms, execution_ms, InputOutcomeName(outcome));
|
||||
},
|
||||
[this] { return devices_.ResetToInitialDisplay(); },
|
||||
[this](const std::string& id, unsigned mask) {
|
||||
#ifdef _WIN32
|
||||
ScopedUserDesktopInputThread input_thread;
|
||||
#endif
|
||||
return devices_.ReleaseRemoteMouseButtons(id, mask)
|
||||
? InputOutcome::applied : InputOutcome::failed;
|
||||
}) {}
|
||||
|
||||
GuiRuntime::~GuiRuntime() {
|
||||
remote_input_.Stop();
|
||||
#if _WIN32
|
||||
windows_rdp_handoff_allowed_->store(false, std::memory_order_relaxed);
|
||||
windows_rdp_handoff_connected_->store(false, std::memory_order_relaxed);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "features/devices/session_device_manager.h"
|
||||
#include "features/file_transfer/file_transfer_manager.h"
|
||||
#include "features/input/keyboard_controller.h"
|
||||
#include "features/input/remote_input_dispatcher.h"
|
||||
#include "features/settings/settings_manager.h"
|
||||
#include "privacy_controller.h"
|
||||
#include "runtime/gui_state.h"
|
||||
@@ -90,6 +91,8 @@ class GuiRuntime : protected gui_detail::GuiState {
|
||||
// UI thread: send mapped mouse/keyboard input to the current control target,
|
||||
// or every connected session while that target leads DeskHub group control.
|
||||
void SendRemoteInput(const RemoteAction& action);
|
||||
InputOutcome ApplyRemoteInput(const std::string& remote_id, const RemoteAction& action);
|
||||
InputOutcome InjectRemoteMouse(const RemoteAction& action, int display);
|
||||
std::string gallery_group_control_id_;
|
||||
|
||||
void ResetRemoteServiceStatus(RemoteSession& props);
|
||||
@@ -133,6 +136,7 @@ class GuiRuntime : protected gui_detail::GuiState {
|
||||
FileTransferManager transfers_;
|
||||
SettingsManager settings_;
|
||||
KeyboardController keyboard_;
|
||||
RemoteInputDispatcher remote_input_;
|
||||
std::shared_ptr<PeerEventHandler> peer_events_;
|
||||
std::atomic<bool> video_frame_dirty_{false};
|
||||
std::unique_ptr<VideoRenderer> video_renderer_;
|
||||
|
||||
@@ -280,29 +280,22 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
runtime->QueuePrivacyCommand(remote_id, remote_action.pc);
|
||||
return;
|
||||
}
|
||||
if (remote_action.type == ControlType::text_input) {
|
||||
// Share the ordered keyboard stream with delete/return and shortcuts.
|
||||
if (source_id == runtime->keyboard_label_ &&
|
||||
runtime->IsAuthorizedController(remote_id))
|
||||
runtime->keyboard_.ApplyRemoteText(remote_id, remote_action.text);
|
||||
if (RemoteInputDispatcher::Accepts(remote_action)) {
|
||||
if (!runtime->IsAuthorizedController(remote_id)) return;
|
||||
if (remote_action.type == ControlType::text_input &&
|
||||
source_id != runtime->keyboard_label_) return;
|
||||
if (runtime->remote_input_.Push(remote_id, remote_action) ==
|
||||
RemoteInputDispatcher::Result::overflow)
|
||||
LOG_WARN("Remote input queue overflow: peer={}, clearing stale input and "
|
||||
"releasing keys/buttons",
|
||||
remote_id);
|
||||
return;
|
||||
}
|
||||
const bool operates_host = remote_action.type == ControlType::mouse ||
|
||||
remote_action.type == ControlType::keyboard || remote_action.type == ControlType::keyboard_state ||
|
||||
remote_action.type == ControlType::service_command || remote_action.type == ControlType::display_id ||
|
||||
remote_action.type == ControlType::audio_capture;
|
||||
if (operates_host) {
|
||||
if (remote_action.type == ControlType::audio_capture) {
|
||||
if (!runtime->IsAuthorizedController(remote_id)) return;
|
||||
#ifdef _WIN32
|
||||
// KeyboardController checks the desktop immediately before each native
|
||||
// key injection, including state reconciliation and cleanup. Avoid a
|
||||
// second synchronous session query for every received keyboard packet.
|
||||
const bool keyboard_input = remote_action.type == ControlType::keyboard ||
|
||||
remote_action.type == ControlType::keyboard_state;
|
||||
if (!keyboard_input && runtime->privacy_.Engaged() &&
|
||||
!IsWindowsPrivacyDesktopAvailable()) {
|
||||
if (runtime->privacy_.Engaged() && !IsWindowsPrivacyDesktopAvailable())
|
||||
runtime->privacy_.SuspendForDesktop();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (remote_action.type == ControlType::service_status) {
|
||||
@@ -312,28 +305,6 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote_action.type == ControlType::service_command) {
|
||||
if (runtime->privacy_.Engaged()) {
|
||||
runtime->privacy_.Fail("Security shortcut requested; privacy screen will turn off");
|
||||
}
|
||||
#if _WIN32
|
||||
if (remote_action.c.flag == ServiceCommandFlag::send_sas) {
|
||||
runtime->pending_windows_service_sas_.store(true,
|
||||
std::memory_order_relaxed);
|
||||
} else if (remote_action.c.flag == ServiceCommandFlag::lock_workstation) {
|
||||
if (!LockWorkStation()) {
|
||||
LOG_WARN("Remote lock workstation request failed, error={}",
|
||||
GetLastError());
|
||||
}
|
||||
}
|
||||
#elif defined(__OHOS__)
|
||||
if (!SendHarmonySystemShortcut(remote_action.c.flag))
|
||||
LOG_WARN("Harmony system shortcut unsupported or unauthorized, command={}",
|
||||
static_cast<int>(remote_action.c.flag));
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (remote_action.type == ControlType::cursor_state) {
|
||||
std::shared_ptr<GuiRuntime::RemoteSession> props;
|
||||
{
|
||||
@@ -464,75 +435,7 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
FreeRemoteAction(remote_action);
|
||||
}
|
||||
} else {
|
||||
// remote
|
||||
if (runtime->is_server_mode_ &&
|
||||
remote_action.type == ControlType::mouse) {
|
||||
std::lock_guard lock(runtime->remote_pointer_input_mutex_);
|
||||
runtime->last_remote_pointer_input_time_[remote_id] =
|
||||
std::chrono::steady_clock::now();
|
||||
}
|
||||
#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(),
|
||||
runtime->local_service_available_.load(), process_elevated);
|
||||
},
|
||||
[&] {
|
||||
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(error) || blocked, blocked};
|
||||
},
|
||||
[&]() -> DesktopInputResult {
|
||||
int absolute_x = 0;
|
||||
int absolute_y = 0;
|
||||
if (!BuildAbsoluteMousePosition(
|
||||
runtime->devices_.display_info_list(),
|
||||
runtime->selected_display_, remote_action.m.x,
|
||||
remote_action.m.y, &absolute_x, &absolute_y)) {
|
||||
LOG_WARN(
|
||||
"Secure desktop mouse injection skipped, invalid display "
|
||||
"mapping: display_index={}, x={}, y={}",
|
||||
runtime->selected_display_, remote_action.m.x,
|
||||
remote_action.m.y);
|
||||
return {};
|
||||
}
|
||||
|
||||
response = runtime->devices_.SendServiceMouseCommand(
|
||||
absolute_x, absolute_y, remote_action.m.s,
|
||||
static_cast<int>(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 =
|
||||
json.is_object() ? json.value("error", "") : "";
|
||||
return {sent, IsDesktopInputSetupPending(error)};
|
||||
});
|
||||
if (!delivered && !response.empty()) {
|
||||
LogSecureDesktopInputBlocked(
|
||||
&runtime->last_local_secure_input_block_log_tick_, "local",
|
||||
runtime->WindowsInputStage().c_str());
|
||||
LOG_WARN(
|
||||
"Secure desktop mouse injection failed, x={}, y={}, wheel={}, "
|
||||
"flag={}, response={}",
|
||||
remote_action.m.x, remote_action.m.y, remote_action.m.s,
|
||||
static_cast<int>(remote_action.m.flag), response);
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
runtime->devices_.SendMouseCommand(remote_action,
|
||||
runtime->selected_display_);
|
||||
} else if (remote_action.type == ControlType::audio_capture) {
|
||||
if (remote_action.type == ControlType::audio_capture) {
|
||||
// A negotiated subscription remains authoritative if a legacy capture
|
||||
// command arrives after it (e.g. when unmuting an inactive window).
|
||||
const auto subscription = runtime->preview_subscriptions_.Get(remote_id);
|
||||
@@ -544,20 +447,139 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
||||
else if (ret != 0)
|
||||
// Legacy/web transports without the per-peer API retain their fallback.
|
||||
runtime->devices_.StopSpeakerCapturer();
|
||||
} else if (remote_action.type == ControlType::keyboard) {
|
||||
runtime->keyboard_.ApplyRemoteEvent(remote_id, remote_action);
|
||||
} else if (remote_action.type == ControlType::keyboard_state) {
|
||||
runtime->keyboard_.ApplyRemoteState(remote_id, remote_action);
|
||||
} else if (remote_action.type == ControlType::display_id) {
|
||||
const int ret = runtime->devices_.SwitchDisplay(remote_action.d);
|
||||
if (ret == 0) {
|
||||
runtime->selected_display_ = remote_action.d;
|
||||
} else {
|
||||
LOG_WARN("Display switch skipped, invalid display_id={}",
|
||||
remote_action.d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
InputOutcome GuiRuntime::ApplyRemoteInput(const std::string& remote_id,
|
||||
const RemoteAction& remote_action) {
|
||||
if (!IsAuthorizedController(remote_id)) return InputOutcome::ignored;
|
||||
if (remote_action.type == ControlType::text_input) {
|
||||
return keyboard_.ApplyRemoteText(remote_id, remote_action.text);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
const bool keyboard_input = remote_action.type == ControlType::keyboard ||
|
||||
remote_action.type == ControlType::keyboard_state;
|
||||
if (!keyboard_input && privacy_.Engaged() &&
|
||||
!IsWindowsPrivacyDesktopAvailable())
|
||||
privacy_.SuspendForDesktop();
|
||||
#endif
|
||||
if (remote_action.type == ControlType::service_command) {
|
||||
if (privacy_.Engaged()) {
|
||||
privacy_.Fail(
|
||||
"Security shortcut requested; privacy screen will turn off");
|
||||
}
|
||||
#if _WIN32
|
||||
if (remote_action.c.flag == ServiceCommandFlag::send_sas) {
|
||||
pending_windows_service_sas_.store(true, std::memory_order_relaxed);
|
||||
return InputOutcome::applied;
|
||||
} else if (remote_action.c.flag == ServiceCommandFlag::lock_workstation) {
|
||||
if (!LockWorkStation()) {
|
||||
LOG_WARN("Remote lock workstation request failed, error={}",
|
||||
GetLastError());
|
||||
return InputOutcome::failed;
|
||||
}
|
||||
return InputOutcome::applied;
|
||||
}
|
||||
#elif defined(__OHOS__)
|
||||
if (!SendHarmonySystemShortcut(remote_action.c.flag)) {
|
||||
LOG_WARN(
|
||||
"Harmony system shortcut unsupported or unauthorized, command={}",
|
||||
static_cast<int>(remote_action.c.flag));
|
||||
return InputOutcome::failed;
|
||||
}
|
||||
return InputOutcome::applied;
|
||||
#endif
|
||||
return InputOutcome::ignored;
|
||||
}
|
||||
|
||||
const int display = selected_display_.load();
|
||||
if (is_server_mode_ && remote_action.type == ControlType::mouse) {
|
||||
std::lock_guard lock(remote_pointer_input_mutex_);
|
||||
last_remote_pointer_input_time_[remote_id] =
|
||||
std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
if (remote_action.type == ControlType::mouse) {
|
||||
return devices_.ApplyRemoteMouse(remote_id, remote_action.m.flag,
|
||||
[this, &remote_action, display] {
|
||||
return InjectRemoteMouse(remote_action, display);
|
||||
});
|
||||
} else if (remote_action.type == ControlType::keyboard) {
|
||||
return keyboard_.ApplyRemoteEvent(remote_id, remote_action);
|
||||
} else if (remote_action.type == ControlType::keyboard_state) {
|
||||
return keyboard_.ApplyRemoteState(remote_id, remote_action);
|
||||
} else if (remote_action.type == ControlType::display_id) {
|
||||
const int ret = devices_.SwitchDisplay(remote_action.d);
|
||||
if (ret == 0) {
|
||||
selected_display_ = remote_action.d;
|
||||
return InputOutcome::applied;
|
||||
} else {
|
||||
LOG_WARN("Display switch skipped, invalid display_id={}",
|
||||
remote_action.d);
|
||||
return InputOutcome::failed;
|
||||
}
|
||||
}
|
||||
return InputOutcome::ignored;
|
||||
}
|
||||
|
||||
InputOutcome GuiRuntime::InjectRemoteMouse(const RemoteAction& remote_action,
|
||||
int display) {
|
||||
#if _WIN32
|
||||
if (is_server_mode_) {
|
||||
static const bool process_elevated = IsAdministratorProcess();
|
||||
std::string response;
|
||||
const bool delivered = DispatchDesktopInput(
|
||||
[&] {
|
||||
return PreferUserDesktopInput(
|
||||
IsCurrentSessionUserDesktopActive(), windows_consent_ui_.load(),
|
||||
local_service_available_.load(), process_elevated);
|
||||
},
|
||||
[&] {
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
const bool sent = devices_.SendMouseCommand(remote_action, display);
|
||||
const DWORD error = GetLastError();
|
||||
const bool blocked =
|
||||
error == ERROR_ACCESS_DENIED || error == ERROR_GEN_FAILURE;
|
||||
return DesktopInputResult{
|
||||
sent, IsDesktopTransitionInputError(error) || blocked, blocked};
|
||||
},
|
||||
[&]() -> DesktopInputResult {
|
||||
int absolute_x = 0;
|
||||
int absolute_y = 0;
|
||||
if (!BuildAbsoluteMousePosition(devices_.display_info_list(), display,
|
||||
remote_action.m.x, remote_action.m.y,
|
||||
&absolute_x, &absolute_y)) {
|
||||
LOG_WARN(
|
||||
"Secure desktop mouse injection skipped, invalid display "
|
||||
"mapping: display_index={}, x={}, y={}",
|
||||
display, remote_action.m.x, remote_action.m.y);
|
||||
return {};
|
||||
}
|
||||
|
||||
response = devices_.SendServiceMouseCommand(
|
||||
absolute_x, absolute_y, remote_action.m.s,
|
||||
static_cast<int>(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 =
|
||||
json.is_object() ? json.value("error", "") : "";
|
||||
return {sent, IsDesktopInputSetupPending(error)};
|
||||
});
|
||||
if (!delivered && !response.empty()) {
|
||||
LogSecureDesktopInputBlocked(&last_local_secure_input_block_log_tick_,
|
||||
"local", WindowsInputStage().c_str());
|
||||
LOG_WARN(
|
||||
"Secure desktop mouse injection failed, x={}, y={}, wheel={}, "
|
||||
"flag={}, response={}",
|
||||
remote_action.m.x, remote_action.m.y, remote_action.m.s,
|
||||
static_cast<int>(remote_action.m.flag), response);
|
||||
}
|
||||
return delivered ? InputOutcome::applied : InputOutcome::failed;
|
||||
}
|
||||
#endif
|
||||
return devices_.SendMouseCommand(remote_action, display)
|
||||
? InputOutcome::applied : InputOutcome::failed;
|
||||
}
|
||||
|
||||
} // namespace crossdesk
|
||||
|
||||
@@ -437,6 +437,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
||||
runtime->ApplyPrivacyAdmission(automatic_enable);
|
||||
}
|
||||
runtime->connection_status_[remote_id] = status;
|
||||
if (status == ConnectionStatus::Connected)
|
||||
runtime->remote_input_.Connect(remote_id);
|
||||
#if _WIN32
|
||||
runtime->windows_rdp_handoff_connected_->store(std::any_of(
|
||||
runtime->connection_status_.begin(), runtime->connection_status_.end(),
|
||||
|
||||
Reference in New Issue
Block a user