mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
[fix] disable privacy screen for legacy and web clients
This commit is contained in:
@@ -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)) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user