From b0d6e8a520c45e8efd81ed8838de29ea0e25000f Mon Sep 17 00:00:00 2001 From: kunkundi Date: Thu, 8 Oct 2026 16:12:38 +0800 Subject: [PATCH] [fix] restore automatic privacy screen after Windows unlock --- .../features/input/keyboard_controller.cpp | 2 +- .../src/gui/runtime/peer_data_callbacks.cpp | 2 +- .../src/gui/runtime/privacy_runtime.cpp | 5 ++- .../screen_capturer/screen_capturer_win.cpp | 27 ++++++++++---- .../src/privacy/privacy_controller.cpp | 36 +++++++++++++++++++ apps/desktop/src/privacy/privacy_controller.h | 7 ++++ 6 files changed, 70 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/gui/features/input/keyboard_controller.cpp b/apps/desktop/src/gui/features/input/keyboard_controller.cpp index 310edde9..c4c19537 100644 --- a/apps/desktop/src/gui/features/input/keyboard_controller.cpp +++ b/apps/desktop/src/gui/features/input/keyboard_controller.cpp @@ -216,7 +216,7 @@ bool KeyboardController::InjectRemoteKey(int key_code, bool is_down, uint32_t scan_code, bool extended) { #if _WIN32 if (owner_.privacy_.Engaged() && !IsWindowsPrivacyDesktopAvailable()) { - owner_.privacy_.Fail("Secure desktop or locked session; privacy screen will turn off"); + owner_.privacy_.SuspendForDesktop(); } if (owner_.local_service_status_received_ && IsSecureDesktopInteractionRequired(owner_.local_interactive_stage_)) { diff --git a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp index 4286ae15..1de8b17a 100644 --- a/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp +++ b/apps/desktop/src/gui/runtime/peer_data_callbacks.cpp @@ -235,7 +235,7 @@ void PeerEventHandler::OnReceiveDataBuffer( remote_action.type == ControlType::keyboard_state; if (!keyboard_input && runtime->privacy_.Engaged() && !IsWindowsPrivacyDesktopAvailable()) { - runtime->privacy_.Fail("Secure desktop or lock screen; privacy screen will turn off"); + runtime->privacy_.SuspendForDesktop(); } #endif } diff --git a/apps/desktop/src/gui/runtime/privacy_runtime.cpp b/apps/desktop/src/gui/runtime/privacy_runtime.cpp index e91de5fb..f50e3aab 100644 --- a/apps/desktop/src/gui/runtime/privacy_runtime.cpp +++ b/apps/desktop/src/gui/runtime/privacy_runtime.cpp @@ -46,7 +46,7 @@ void GuiRuntime::SetControllerPrivacySupport(const std::string& remote_id, void GuiRuntime::ApplyPrivacyAdmission(bool automatic_enable) { if (!privacy_sessions_.CanEnable()) { - if (privacy_.Engaged()) privacy_.Disable(); + privacy_.Disable(); } else if (automatic_enable) { privacy_.EnableOnConnection(); } @@ -93,6 +93,9 @@ void GuiRuntime::HandlePrivacy() { std::vector controllers; { std::shared_lock lock(connection_status_mutex_); + if (!privacy_sessions_.CanEnable() || + !config_center_->IsEnablePrivacyScreen()) + privacy_.CancelAutomaticResume(); status = privacy_.Snapshot(); for (const auto& [id, state] : connection_status_) if (state == ConnectionStatus::Connected && diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp index dfee133e..25b3c835 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp @@ -1627,6 +1627,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { ULONGLONG last_error_tick = 0; ULONGLONG capture_stage_started_tick = 0; bool post_secure_restart_pending = false; + bool desktop_was_unavailable = false; ULONGLONG post_secure_restart_deadline_tick = 0; ULONGLONG last_post_secure_restart_tick = 0; ULONGLONG next_cursor_update_tick = 0; @@ -1669,11 +1670,12 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { }; while (running_.load(std::memory_order_relaxed)) { - // Privacy covers belong to the ordinary desktop. Drop privacy on a - // secure desktop, while continuing the normal helper capture path. - if (privacy_ && privacy_->Engaged()) { - if (!IsWindowsPrivacyDesktopAvailable()) - privacy_->Fail("Secure desktop or lock screen; privacy screen will turn off. Privacy cannot cover Windows security UI."); + // Remember automatic privacy even if the connection started while locked + // and its first enable attempt has already failed. + const bool desktop_available = IsWindowsPrivacyDesktopAvailable(); + if (!desktop_available) { + desktop_was_unavailable = true; + if (privacy_) privacy_->SuspendForDesktop(); } if (paused_.load(std::memory_order_relaxed)) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); @@ -1750,12 +1752,23 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { } } + // A locked session can still report Default, and helper status can be + // unavailable. Observe local unlocks independently so recovery publishes + // a fresh capture-running notification in either case. + if (desktop_available && desktop_was_unavailable) { + desktop_was_unavailable = false; + post_secure_restart_pending = true; + post_secure_restart_deadline_tick = + now + kPostSecureDesktopRestartTimeoutMs; + last_post_secure_restart_tick = 0; + } + if (!status.capture_active || status.active_session_id == 0xFFFFFFFF) { StopSecureDesktopSharedCapture(secure_shared_session_id_); CheckDisplayPresence(now); MaybeAdoptVirtualDisplay(); CheckCaptureProgress(now); - if (post_secure_restart_pending) { + if (post_secure_restart_pending && desktop_available) { if (now >= post_secure_restart_deadline_tick) { LOG_WARN( "Windows capturer: capture backend restart after secure desktop " @@ -1767,6 +1780,8 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { last_post_secure_restart_tick = now; post_secure_restart_pending = !RestartCaptureBackendAfterSecureDesktop(); + if (!post_secure_restart_pending && privacy_) + privacy_->ResumeAfterDesktopRecovery(); } } if (!post_secure_restart_pending && now >= next_cursor_update_tick) { diff --git a/apps/desktop/src/privacy/privacy_controller.cpp b/apps/desktop/src/privacy/privacy_controller.cpp index 26c3b93c..f22cd341 100644 --- a/apps/desktop/src/privacy/privacy_controller.cpp +++ b/apps/desktop/src/privacy/privacy_controller.cpp @@ -64,6 +64,7 @@ void PrivacyController::Enable(bool block_input) { engaged_.store(true); block_requested_ = block_input; automatic_enable_ = false; + resume_after_unlock_ = false; enable_pending_ = true; failure_.clear(); started_ = Now(); @@ -72,11 +73,15 @@ void PrivacyController::Enable(bool block_input) { } void PrivacyController::EnableOnConnection() { std::lock_guard lock(mutex_); + EnableOnConnectionLocked(); +} +void PrivacyController::EnableOnConnectionLocked() { if (quit_ || (engaged_.load() && !disable_pending_)) return; // A reconnect may arrive before asynchronous AppKit recovery has completed. engaged_.store(true); block_requested_ = true; automatic_enable_ = true; + resume_after_unlock_ = false; enable_pending_ = true; failure_.clear(); started_ = Now(); @@ -85,6 +90,12 @@ void PrivacyController::EnableOnConnection() { } void PrivacyController::Disable() { std::lock_guard lock(mutex_); + DisableLocked(); +} +void PrivacyController::DisableLocked() { + // A suspended session is no longer engaged, but still has automatic intent. + automatic_enable_ = false; + resume_after_unlock_ = false; // Nothing to restore when privacy is already off and no cleanup is retrying. if (quit_ || (!engaged_.load() && !disable_pending_)) return; enable_pending_ = false; @@ -94,6 +105,29 @@ void PrivacyController::Disable() { WakeLocked(); } void PrivacyController::Disconnected() { Disable(); } +void PrivacyController::SuspendForDesktop() { + std::lock_guard lock(mutex_); + if (quit_) return; + if (automatic_enable_) resume_after_unlock_ = true; + FailLocked(automatic_enable_ + ? "Secure desktop or lock screen; automatic privacy will resume after desktop recovery" + : "Secure desktop or lock screen; privacy screen will turn off"); +} +void PrivacyController::ResumeAfterDesktopRecovery() { + std::lock_guard lock(mutex_); + // Wait for the entire post-unlock restart, including display selection. + // Other capture-running notifications can arrive earlier during recovery. + if (capture_running_ && resume_after_unlock_ && automatic_enable_) + EnableOnConnectionLocked(); +} +void PrivacyController::CancelAutomaticResume() { + std::lock_guard lock(mutex_); + resume_after_unlock_ = false; + // Changing the setting cancels pending activation, but leaves an already + // active cover under the explicit on/off controls. + if (automatic_enable_ && enable_pending_) DisableLocked(); + automatic_enable_ = false; +} void PrivacyController::FailLocked(const std::string& reason) { if (!engaged_.load() || disable_pending_) return; failure_ = reason; @@ -196,6 +230,8 @@ void PrivacyController::Run(Factory factory) { const auto health = backend_ ? backend_->Poll() : PrivacyHealth{}; if (engaged_.load() && !disable_pending_) { if (health.emergency_exit) { + automatic_enable_ = false; + resume_after_unlock_ = false; failure_.clear(); enable_pending_ = false; disable_pending_ = true; diff --git a/apps/desktop/src/privacy/privacy_controller.h b/apps/desktop/src/privacy/privacy_controller.h index 3ad1a6d7..5e57d13d 100644 --- a/apps/desktop/src/privacy/privacy_controller.h +++ b/apps/desktop/src/privacy/privacy_controller.h @@ -25,6 +25,10 @@ class PrivacyController { void SetText(const PrivacyScreenText& text); void Enable(bool block_input); void EnableOnConnection(); + // Preserve automatic session intent while Windows shows another desktop. + void SuspendForDesktop(); + void ResumeAfterDesktopRecovery(); + void CancelAutomaticResume(); void Disable(); void Disconnected(); void Shutdown(); @@ -37,6 +41,8 @@ class PrivacyController { void WakeLocked(); void SetState(PrivacyState state, const std::string& reason); void FailLocked(const std::string& reason); + void EnableOnConnectionLocked(); + void DisableLocked(); static uint64_t Now(); mutable std::mutex mutex_; std::condition_variable wake_; @@ -50,6 +56,7 @@ class PrivacyController { bool disable_pending_ = false; bool block_requested_ = false; bool automatic_enable_ = false; + bool resume_after_unlock_ = false; bool capture_running_ = false; std::string failure_; uint64_t started_ = 0;