[fix] keep Windows privacy screen across display changes and validate covers

This commit is contained in:
dijunkun
2026-09-21 01:10:30 +08:00
parent 276385b816
commit fb556a763d
12 changed files with 296 additions and 134 deletions
@@ -64,6 +64,9 @@ class GuiRuntime : protected gui_detail::GuiState {
void HandlePrivacy();
bool IsAuthorizedController(const std::string& remote_id);
void SetControllerPrivacySupport(const std::string& remote_id, bool supported);
// Caller holds connection_status_mutex_ exclusively. Turns privacy off when
// any connected controller lacks support, or on for the first automatic one.
void ApplyPrivacyAdmission(bool automatic_enable);
void QueuePrivacyCommand(const std::string& remote_id, const PrivacyCommand& command);
void CloseRemoteSession(std::shared_ptr<RemoteSession> props);
@@ -363,14 +363,11 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
{
std::unique_lock lock(runtime->connection_status_mutex_);
if (status == ConnectionStatus::Connected) {
// Wait for explicit desktop capability before enabling privacy or
// sending privacy messages. Legacy and web controllers never opt in.
runtime->privacy_sessions_.Connected(
// Local privacy requires explicit desktop capability. Host info may
// arrive before Connected; consume that saved capability here.
const bool automatic_enable = runtime->privacy_sessions_.Connected(
remote_id, runtime->config_center_->IsEnablePrivacyScreen());
if (!runtime->privacy_sessions_.CanEnable() &&
runtime->privacy_.Engaged()) {
runtime->privacy_.Disable();
}
runtime->ApplyPrivacyAdmission(automatic_enable);
}
runtime->connection_status_[remote_id] = status;
runtime->devices_.OnControllerConnectionsChanged();
@@ -25,14 +25,27 @@ void GuiRuntime::SetControllerPrivacySupport(const std::string& remote_id,
bool supported) {
std::unique_lock lock(connection_status_mutex_);
const auto found = connection_status_.find(remote_id);
if (found == connection_status_.end() ||
found->second != ConnectionStatus::Connected) return;
if (found == connection_status_.end()) return;
if (found->second != ConnectionStatus::Connected) {
if (found->second == ConnectionStatus::Connecting ||
found->second == ConnectionStatus::Gathering) {
privacy_sessions_.RememberSupport(remote_id, supported);
LOG_INFO("Privacy: retained early controller capability, peer={}, supported={}",
remote_id, supported);
}
return;
}
const bool automatic_enable =
privacy_sessions_.SetSupported(remote_id, supported);
ApplyPrivacyAdmission(automatic_enable &&
config_center_->IsEnablePrivacyScreen());
}
void GuiRuntime::ApplyPrivacyAdmission(bool automatic_enable) {
if (!privacy_sessions_.CanEnable()) {
if (privacy_.Engaged()) privacy_.Disable();
} else if (automatic_enable && config_center_->IsEnablePrivacyScreen()) {
} else if (automatic_enable) {
privacy_.EnableOnConnection();
}
}
@@ -45,8 +58,10 @@ void GuiRuntime::QueuePrivacyCommand(const std::string& remote_id,
found->second != ConnectionStatus::Connected ||
!privacy_sessions_.Supports(remote_id)) return;
std::lock_guard lock(privacy_commands_mutex_);
if (privacy_commands_.size() < 32)
privacy_commands_.emplace_back(remote_id, command);
if (privacy_commands_.size() >= 32) return;
LOG_INFO("Privacy: queued controller command, peer={}, flag={}", remote_id,
static_cast<int>(command.flag));
privacy_commands_.emplace_back(remote_id, command);
}
void GuiRuntime::HandlePrivacy() {
@@ -71,17 +86,15 @@ void GuiRuntime::HandlePrivacy() {
}
last_privacy_status_tick_ = 0;
}
auto status = privacy_.Snapshot();
PrivacyStatus status{};
const uint64_t now = SDL_GetTicks();
if (!peer_ || (last_privacy_status_tick_ != 0 &&
status.revision == last_privacy_revision_ &&
now - last_privacy_status_tick_ < 1000))
return;
std::vector<std::string> controllers;
{
std::shared_lock lock(connection_status_mutex_);
status = privacy_.Snapshot();
for (const auto& [id, state] : connection_status_)
if (state == ConnectionStatus::Connected && privacy_sessions_.Supports(id))
if (state == ConnectionStatus::Connected &&
privacy_sessions_.Supports(id))
controllers.push_back(id);
if (!privacy_sessions_.CanEnable()) {
status.state = PrivacyState::unsupported;
@@ -91,6 +104,10 @@ void GuiRuntime::HandlePrivacy() {
"A connected controller does not support privacy screen");
}
}
if (!peer_ || (last_privacy_status_tick_ != 0 &&
status.revision == last_privacy_revision_ &&
now - last_privacy_status_tick_ < 1000))
return;
RemoteAction action{};
action.type = ControlType::privacy_status;
action.ps = status;
@@ -13,19 +13,37 @@
namespace crossdesk {
// Guarded by GuiRuntime's connection_status_mutex_. Privacy covers the shared
// desktop, so every connected controller must explicitly support it.
// Guarded by GuiRuntime's connection_status_mutex_. Privacy covers the local
// displays, so every connected controller must explicitly support its controls.
class PrivacySessionPolicy {
public:
void Connected(const std::string& remote_id, bool automatic_privacy) {
// Web controllers render through the browser and expose no privacy UI.
static bool IsWebController(const std::string& remote_id) {
return remote_id.find("web") != std::string::npos;
}
bool Connected(const std::string& remote_id, bool automatic_privacy) {
const auto [entry, inserted] = controllers_.try_emplace(remote_id, false);
if (inserted && controllers_.size() == 1 && automatic_privacy) {
if (inserted && controllers_.size() == 1 && automatic_privacy &&
!IsWebController(remote_id)) {
automatic_controller_ = remote_id;
}
const auto early = early_support_.find(remote_id);
if (early == early_support_.end()) return false;
const bool supported = early->second;
early_support_.erase(early);
return SetSupported(remote_id, supported);
}
// Only transport-authenticated metadata from a connecting session is cached
// by the caller. It grants no control until Connected admits that session.
void RememberSupport(const std::string& remote_id, bool supported) {
early_support_[remote_id] = supported;
}
void Disconnected(const std::string& remote_id) {
controllers_.erase(remote_id);
early_support_.erase(remote_id);
if (automatic_controller_ == remote_id) automatic_controller_.clear();
}
@@ -34,7 +52,7 @@ class PrivacySessionPolicy {
bool SetSupported(const std::string& remote_id, bool supported) {
const auto entry = controllers_.find(remote_id);
if (entry == controllers_.end()) return false;
entry->second = supported && remote_id.find("web") == std::string::npos;
entry->second = supported && !IsWebController(remote_id);
if (automatic_controller_ != remote_id) return false;
automatic_controller_.clear();
return CanEnable();
@@ -53,9 +71,10 @@ class PrivacySessionPolicy {
private:
std::unordered_map<std::string, bool> controllers_;
std::unordered_map<std::string, bool> early_support_;
std::string automatic_controller_;
};
} // namespace crossdesk
#endif
#endif