#include "runtime/gui_runtime.h" #include #include #include #include #include "rd_log.h" #if _WIN32 #include "interactive_state.h" #include "service_host.h" #endif namespace crossdesk { namespace { #if _WIN32 struct WindowsServiceInteractiveStatus { bool available = false; bool sas_secure_desktop_grace_active = false; unsigned int error_code = 0; std::string interactive_stage; std::string error; }; constexpr uint32_t kWindowsServiceStatusIntervalMs = 1000; constexpr uint32_t kWindowsServiceSasSecureDesktopGraceMs = 2000; constexpr DWORD kWindowsServiceQueryTimeoutMs = 500; constexpr DWORD kWindowsServiceSasTimeoutMs = 500; bool IsTransientWindowsServiceStatusError(const std::string &error) { return error == "pipe_unavailable" || error == "pipe_connect_failed" || error == "pipe_read_failed"; } RemoteAction BuildWindowsServiceStatusAction(const WindowsServiceInteractiveStatus &status) { RemoteAction action{}; action.type = ControlType::service_status; action.ss.available = status.available; std::strncpy(action.ss.interactive_stage, status.interactive_stage.c_str(), sizeof(action.ss.interactive_stage) - 1); action.ss.interactive_stage[sizeof(action.ss.interactive_stage) - 1] = '\0'; return action; } bool QueryWindowsServiceInteractiveStatus( WindowsServiceInteractiveStatus *status) { if (status == nullptr) { return false; } *status = WindowsServiceInteractiveStatus{}; const std::string response = QueryCrossDeskService("status", kWindowsServiceQueryTimeoutMs); auto json = nlohmann::json::parse(response, nullptr, false); if (json.is_discarded() || !json.is_object()) { status->error = "invalid_service_status_json"; return false; } status->available = json.value("ok", false); if (!status->available) { status->error = json.value("error", std::string("service_unavailable")); status->error_code = json.value("code", 0u); return true; } status->interactive_stage = json.value("interactive_stage", std::string()); status->sas_secure_desktop_grace_active = json.value("sas_secure_desktop_grace_active", false); if (ShouldNormalizeUnlockToUserDesktop( json.value("interactive_lock_screen_visible", false), status->interactive_stage, json.value("session_locked", false), json.value("interactive_logon_ui_visible", false), json.value("interactive_secure_desktop_active", json.value("secure_desktop_active", false)), json.value("credential_ui_visible", false), json.value("password_box_visible", false), json.value("unlock_ui_visible", false), json.value("last_session_event", std::string()))) { status->interactive_stage = "user-desktop"; } return true; } #endif } // namespace void GuiRuntime::ResetRemoteServiceStatus(RemoteSession &props) { props.remote_service_status_received_ = false; props.remote_service_available_ = false; props.remote_interactive_stage_.clear(); } void GuiRuntime::ApplyRemoteServiceStatus(RemoteSession &props, const ServiceStatus &status) { props.remote_service_status_received_ = true; props.remote_service_available_ = status.available; props.remote_interactive_stage_ = status.interactive_stage; } GuiRuntime::RemoteUnlockState GuiRuntime::GetRemoteUnlockState(const RemoteSession &props) const { if (!props.remote_service_status_received_) { return RemoteUnlockState::none; } if (!props.remote_service_available_) { return RemoteUnlockState::service_unavailable; } if (props.remote_interactive_stage_ == "credential-ui") { return RemoteUnlockState::credential_ui; } if (props.remote_interactive_stage_ == "lock-screen") { return RemoteUnlockState::lock_screen; } if (props.remote_interactive_stage_ == "secure-desktop") { return RemoteUnlockState::secure_desktop; } return RemoteUnlockState::none; } void GuiRuntime::HandleWindowsServiceIntegration() { #if _WIN32 static bool last_logged_service_available = true; static unsigned int last_logged_service_error_code = 0; static std::string last_logged_service_error; if (!is_server_mode_ || peer_ == nullptr) { ResetLocalWindowsServiceState(true); return; } const bool has_connected_remote = [&] { std::shared_lock lock(connection_status_mutex_); return std::any_of(connection_status_.begin(), connection_status_.end(), [](const auto &entry) { return entry.second == ConnectionStatus::Connected; }); }(); if (!has_connected_remote) { ResetLocalWindowsServiceState(false); return; } bool force_broadcast = false; if (pending_windows_service_sas_.exchange(false, std::memory_order_relaxed)) { const std::string response = QueryCrossDeskService("sas", kWindowsServiceSasTimeoutMs); auto json = nlohmann::json::parse(response, nullptr, false); if (json.is_discarded() || !json.value("ok", false)) { LOG_WARN("Remote SAS request failed: {}", response); } else { LOG_INFO("Remote SAS request forwarded to local Windows service"); optimistic_windows_secure_desktop_until_tick_ = static_cast(SDL_GetTicks()) + kWindowsServiceSasSecureDesktopGraceMs; local_service_status_received_ = true; local_service_available_ = true; local_interactive_stage_ = "secure-desktop"; } last_windows_service_status_tick_ = 0; force_broadcast = true; } const uint32_t now = static_cast(SDL_GetTicks()); if (!force_broadcast && last_windows_service_status_tick_ != 0 && now - last_windows_service_status_tick_ < kWindowsServiceStatusIntervalMs) { return; } last_windows_service_status_tick_ = now; WindowsServiceInteractiveStatus status; const bool status_ok = QueryWindowsServiceInteractiveStatus(&status); WindowsServiceInteractiveStatus broadcast_status = status; const bool previous_secure_desktop_interaction = IsSecureDesktopInteractionRequired(local_interactive_stage_); const bool optimistic_secure_desktop_active = optimistic_windows_secure_desktop_until_tick_ != 0 && static_cast(optimistic_windows_secure_desktop_until_tick_ - now) > 0; const bool keep_optimistic_secure_desktop = status_ok && status.available && optimistic_secure_desktop_active && status.sas_secure_desktop_grace_active && status.interactive_stage == "user-desktop"; local_service_status_received_ = status_ok || previous_secure_desktop_interaction; local_service_available_ = status.available; if (status.available) { if (keep_optimistic_secure_desktop) { local_interactive_stage_ = "secure-desktop"; broadcast_status.interactive_stage = local_interactive_stage_; } else { local_interactive_stage_ = status.interactive_stage; optimistic_windows_secure_desktop_until_tick_ = 0; } } else if (!previous_secure_desktop_interaction) { local_interactive_stage_.clear(); optimistic_windows_secure_desktop_until_tick_ = 0; } if (status_ok) { const bool availability_changed = status.available != last_logged_service_available; const bool error_changed = !status.available && (status.error != last_logged_service_error || status.error_code != last_logged_service_error_code); if (availability_changed || error_changed) { if (status.available) { LOG_INFO( "Local Windows service available for secure desktop integration"); } else if (IsTransientWindowsServiceStatusError(status.error)) { LOG_INFO("Local Windows service temporarily unavailable, keeping last " "secure desktop state: error={}, code={}", status.error, status.error_code); } else { LOG_WARN( "Local Windows service unavailable, secure desktop integration " "disabled: error={}, code={}", status.error, status.error_code); } last_logged_service_available = status.available; last_logged_service_error = status.error; last_logged_service_error_code = status.error_code; } } else if (last_logged_service_available || last_logged_service_error != "invalid_service_status_json") { LOG_WARN( "Local Windows service status query failed, secure desktop integration " "disabled"); last_logged_service_available = false; last_logged_service_error = "invalid_service_status_json"; last_logged_service_error_code = 0; } RemoteAction remote_action = BuildWindowsServiceStatusAction(broadcast_status); std::string msg = remote_action.to_json(); int ret = SendReliableDataFrame(peer_, msg.data(), msg.size(), control_data_label_.c_str()); if (ret != 0) { LOG_WARN("Broadcast Windows service status failed, ret={}", ret); } #endif } #if _WIN32 void GuiRuntime::ResetLocalWindowsServiceState(bool clear_pending_sas) { last_windows_service_status_tick_ = 0; if (clear_pending_sas) { pending_windows_service_sas_.store(false, std::memory_order_relaxed); } local_service_status_received_ = false; local_service_available_ = false; local_interactive_stage_.clear(); optimistic_windows_secure_desktop_until_tick_ = 0; } #endif } // namespace crossdesk