[fix] disable privacy screen for legacy and web clients

This commit is contained in:
dijunkun
2026-09-15 13:04:53 +08:00
parent f6324416ec
commit e40c7fca98
9 changed files with 135 additions and 21 deletions
@@ -146,6 +146,7 @@ void GuiRuntime::HandleServerControllerDisconnected(
std::unique_lock lock(connection_status_mutex_);
connection_status_.erase(remote_id);
connection_host_names_.erase(remote_id);
privacy_sessions_.Disconnected(remote_id);
for (const auto& [id, status] : connection_status_) {
if (status != ConnectionStatus::Connected) {
continue;
@@ -20,6 +20,7 @@
#include "privacy_controller.h"
#include "runtime/gui_state.h"
#include "runtime/peer_event_handler.h"
#include "runtime/privacy_session_policy.h"
namespace crossdesk {
@@ -63,6 +64,7 @@ class GuiRuntime : protected gui_detail::GuiState {
void HandleWindowsServiceIntegration();
void HandlePrivacy();
bool IsAuthorizedController(const std::string& remote_id);
void SetControllerPrivacySupport(const std::string& remote_id, bool supported);
void QueuePrivacyCommand(const std::string& remote_id, const PrivacyCommand& command);
void CloseRemoteSession(std::shared_ptr<RemoteSession> props);
@@ -94,6 +96,7 @@ class GuiRuntime : protected gui_detail::GuiState {
#endif
PrivacyController privacy_;
PrivacySessionPolicy privacy_sessions_;
std::mutex privacy_commands_mutex_;
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
uint32_t last_privacy_revision_ = 0;
@@ -290,6 +290,10 @@ void PeerEventHandler::OnReceiveDataBuffer(
runtime->connection_host_names_[remote_id] = host_name;
}
LOG_INFO("Remote hostname: [{}]", host_name);
if (source_id == runtime->control_data_label_) {
runtime->SetControllerPrivacySupport(
remote_id, remote_action.i.supports_privacy_screen);
}
remote_action_codec::Free(remote_action);
}
} else {
@@ -264,7 +264,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
props->remote_cursor_state_received_ = false;
}
{
RemoteAction remote_action;
RemoteAction remote_action{};
remote_action.i.supports_privacy_screen = true;
remote_action.i.display_num =
runtime->devices_.display_info_list().size();
remote_action.i.display_list =
@@ -405,20 +406,16 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
runtime->show_connection_status_window_ = true;
{
std::unique_lock lock(runtime->connection_status_mutex_);
#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__)
if (status == ConnectionStatus::Connected &&
runtime->config_center_->IsEnablePrivacyScreen() &&
std::none_of(runtime->connection_status_.begin(),
runtime->connection_status_.end(), [](const auto& entry) {
return entry.second == ConnectionStatus::Connected;
})) {
// Request optional privacy without delaying connection or capture.
// Only the first controller takes over local input. A later join must
// not release an existing controller's keys or re-enable privacy that
// the current session explicitly turned off.
runtime->privacy_.EnableOnConnection();
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(
remote_id, runtime->config_center_->IsEnablePrivacyScreen());
if (!runtime->privacy_sessions_.CanEnable() &&
runtime->privacy_.Engaged()) {
runtime->privacy_.Disable();
}
}
#endif
runtime->connection_status_[remote_id] = status;
}
@@ -428,7 +425,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
runtime->last_windows_service_status_tick_ = 0;
#endif
{
RemoteAction remote_action;
RemoteAction remote_action{};
remote_action.i.supports_privacy_screen = true;
remote_action.i.display_num =
runtime->devices_.display_info_list().size();
remote_action.i.display_list =
@@ -4,6 +4,7 @@
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#include <cstring>
#include <shared_mutex>
#include "rd_log.h"
@@ -20,9 +21,29 @@ bool GuiRuntime::IsAuthorizedController(const std::string& remote_id) {
found->second == ConnectionStatus::Connected;
}
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;
const bool automatic_enable =
privacy_sessions_.SetSupported(remote_id, supported);
if (!privacy_sessions_.CanEnable()) {
if (privacy_.Engaged()) privacy_.Disable();
} else if (automatic_enable && config_center_->IsEnablePrivacyScreen()) {
privacy_.EnableOnConnection();
}
}
void GuiRuntime::QueuePrivacyCommand(const std::string& remote_id,
const PrivacyCommand& command) {
if (!IsAuthorizedController(remote_id)) return;
std::shared_lock session_lock(connection_status_mutex_);
const auto found = connection_status_.find(remote_id);
if (found == connection_status_.end() ||
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);
@@ -35,15 +56,22 @@ void GuiRuntime::HandlePrivacy() {
commands.swap(privacy_commands_);
}
for (const auto& [remote_id, command] : commands) {
if (!IsAuthorizedController(remote_id)) continue;
// Keep admission and activation under the connection lock so an unknown
// controller cannot join between the capability check and enabling.
std::shared_lock lock(connection_status_mutex_);
const auto found = connection_status_.find(remote_id);
if (found == connection_status_.end() ||
found->second != ConnectionStatus::Connected ||
!privacy_sessions_.Supports(remote_id)) continue;
if (command.flag == PrivacyCommandFlag::enable) {
privacy_.Enable(command.block_local_input);
if (privacy_sessions_.CanEnable())
privacy_.Enable(command.block_local_input);
} else if (command.flag == PrivacyCommandFlag::disable) {
privacy_.Disable();
}
last_privacy_status_tick_ = 0;
}
const auto status = privacy_.Snapshot();
auto status = privacy_.Snapshot();
const uint64_t now = SDL_GetTicks();
if (!peer_ || (last_privacy_status_tick_ != 0 &&
status.revision == last_privacy_revision_ &&
@@ -53,7 +81,15 @@ void GuiRuntime::HandlePrivacy() {
{
std::shared_lock lock(connection_status_mutex_);
for (const auto& [id, state] : connection_status_)
if (state == ConnectionStatus::Connected) controllers.push_back(id);
if (state == ConnectionStatus::Connected && privacy_sessions_.Supports(id))
controllers.push_back(id);
if (!privacy_sessions_.CanEnable()) {
status.state = PrivacyState::unsupported;
status.supported = false;
status.input_block_supported = false;
std::strcpy(status.reason,
"A connected controller does not support privacy screen");
}
}
RemoteAction action{};
action.type = ControlType::privacy_status;
@@ -0,0 +1,61 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-09-15
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _PRIVACY_SESSION_POLICY_H_
#define _PRIVACY_SESSION_POLICY_H_
#include <algorithm>
#include <string>
#include <unordered_map>
namespace crossdesk {
// Guarded by GuiRuntime's connection_status_mutex_. Privacy covers the shared
// desktop, so every connected controller must explicitly support it.
class PrivacySessionPolicy {
public:
void 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) {
automatic_controller_ = remote_id;
}
}
void Disconnected(const std::string& remote_id) {
controllers_.erase(remote_id);
if (automatic_controller_ == remote_id) automatic_controller_.clear();
}
// Consume automatic activation once, on the first controller's host info.
// Repeated metadata or a later join must not undo an explicit privacy exit.
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;
if (automatic_controller_ != remote_id) return false;
automatic_controller_.clear();
return CanEnable();
}
bool Supports(const std::string& remote_id) const {
const auto entry = controllers_.find(remote_id);
return entry != controllers_.end() && entry->second;
}
bool CanEnable() const {
return !controllers_.empty() &&
std::all_of(controllers_.begin(), controllers_.end(),
[](const auto& entry) { return entry.second; });
}
private:
std::unordered_map<std::string, bool> controllers_;
std::string automatic_controller_;
};
} // namespace crossdesk
#endif
@@ -55,6 +55,7 @@ bool Deserialize(const char *data, size_t size, RemoteAction &out) {
out.type = static_cast<ControlType>(data[offset++]);
if (out.type == ControlType::host_infomation) {
out.i.supports_privacy_screen = false;
size_t name_len;
if (!read(&name_len, sizeof(size_t)) ||
name_len >= sizeof(out.i.host_name)) {
+2
View File
@@ -100,6 +100,8 @@ struct HostInfo {
int* top;
int* right;
int* bottom;
// Optional capability; absent in legacy desktop and web host information.
bool supports_privacy_screen;
};
struct ServiceStatus {
+9 -1
View File
@@ -22,6 +22,7 @@ void ResetHostInfo(HostInfo& info) {
info.top = nullptr;
info.right = nullptr;
info.bottom = nullptr;
info.supports_privacy_screen = false;
}
bool AllocateHostDisplays(HostInfo& info, std::size_t count) {
@@ -132,7 +133,9 @@ std::string RemoteAction::ToJson(const RemoteAction& action) {
}
object["host_info"] = {{"host_name", action.i.host_name},
{"display_num", action.i.display_num},
{"displays", displays}};
{"displays", displays},
{"supports_privacy_screen",
action.i.supports_privacy_screen}};
break;
}
case ControlType::invalid:
@@ -272,6 +275,11 @@ bool RemoteAction::FromJson(const std::string& json_string,
ResetHostInfo(output.i);
owns_host_info = true;
const auto& host_info_object = object.at("host_info");
const auto privacy_support =
host_info_object.find("supports_privacy_screen");
output.i.supports_privacy_screen =
privacy_support != host_info_object.end() &&
privacy_support->is_boolean() && privacy_support->get<bool>();
const std::string host_name =
host_info_object.at("host_name").get<std::string>();
std::strncpy(output.i.host_name, host_name.c_str(),