[feat] improve Windows secure desktop capture and input handling, refs #77

This commit is contained in:
dijunkun
2026-05-26 03:26:37 +08:00
parent 52b894fe0e
commit 665f4e684c
10 changed files with 776 additions and 139 deletions
+107 -28
View File
@@ -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<DWORD>((std::min)(
deadline_tick - now, static_cast<ULONGLONG>(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<DWORD>((std::min)(
static_cast<ULONGLONG>(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<DWORD>((std::min)(
static_cast<ULONGLONG>(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<std::mutex> 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<LPWSTR>(L"winsta0\\Winlogon");
std::wstring secure_input_helper_desktop =
SecureInputHelperDesktopForStage(interactive_stage);
startup_info.lpDesktop =
const_cast<LPWSTR>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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) {