mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-18 09:53:05 +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_);
|
std::unique_lock lock(connection_status_mutex_);
|
||||||
connection_status_.erase(remote_id);
|
connection_status_.erase(remote_id);
|
||||||
connection_host_names_.erase(remote_id);
|
connection_host_names_.erase(remote_id);
|
||||||
|
privacy_sessions_.Disconnected(remote_id);
|
||||||
for (const auto& [id, status] : connection_status_) {
|
for (const auto& [id, status] : connection_status_) {
|
||||||
if (status != ConnectionStatus::Connected) {
|
if (status != ConnectionStatus::Connected) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
#include "privacy_controller.h"
|
#include "privacy_controller.h"
|
||||||
#include "runtime/gui_state.h"
|
#include "runtime/gui_state.h"
|
||||||
#include "runtime/peer_event_handler.h"
|
#include "runtime/peer_event_handler.h"
|
||||||
|
#include "runtime/privacy_session_policy.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ class GuiRuntime : protected gui_detail::GuiState {
|
|||||||
void HandleWindowsServiceIntegration();
|
void HandleWindowsServiceIntegration();
|
||||||
void HandlePrivacy();
|
void HandlePrivacy();
|
||||||
bool IsAuthorizedController(const std::string& remote_id);
|
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 QueuePrivacyCommand(const std::string& remote_id, const PrivacyCommand& command);
|
||||||
|
|
||||||
void CloseRemoteSession(std::shared_ptr<RemoteSession> props);
|
void CloseRemoteSession(std::shared_ptr<RemoteSession> props);
|
||||||
@@ -94,6 +96,7 @@ class GuiRuntime : protected gui_detail::GuiState {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
PrivacyController privacy_;
|
PrivacyController privacy_;
|
||||||
|
PrivacySessionPolicy privacy_sessions_;
|
||||||
std::mutex privacy_commands_mutex_;
|
std::mutex privacy_commands_mutex_;
|
||||||
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
|
std::deque<std::pair<std::string, PrivacyCommand>> privacy_commands_;
|
||||||
uint32_t last_privacy_revision_ = 0;
|
uint32_t last_privacy_revision_ = 0;
|
||||||
|
|||||||
@@ -290,6 +290,10 @@ void PeerEventHandler::OnReceiveDataBuffer(
|
|||||||
runtime->connection_host_names_[remote_id] = host_name;
|
runtime->connection_host_names_[remote_id] = host_name;
|
||||||
}
|
}
|
||||||
LOG_INFO("Remote hostname: [{}]", 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);
|
remote_action_codec::Free(remote_action);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -264,7 +264,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
props->remote_cursor_state_received_ = false;
|
props->remote_cursor_state_received_ = false;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
RemoteAction remote_action;
|
RemoteAction remote_action{};
|
||||||
|
remote_action.i.supports_privacy_screen = true;
|
||||||
remote_action.i.display_num =
|
remote_action.i.display_num =
|
||||||
runtime->devices_.display_info_list().size();
|
runtime->devices_.display_info_list().size();
|
||||||
remote_action.i.display_list =
|
remote_action.i.display_list =
|
||||||
@@ -405,20 +406,16 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
runtime->show_connection_status_window_ = true;
|
runtime->show_connection_status_window_ = true;
|
||||||
{
|
{
|
||||||
std::unique_lock lock(runtime->connection_status_mutex_);
|
std::unique_lock lock(runtime->connection_status_mutex_);
|
||||||
#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__)
|
if (status == ConnectionStatus::Connected) {
|
||||||
if (status == ConnectionStatus::Connected &&
|
// Wait for explicit desktop capability before enabling privacy or
|
||||||
runtime->config_center_->IsEnablePrivacyScreen() &&
|
// sending privacy messages. Legacy and web controllers never opt in.
|
||||||
std::none_of(runtime->connection_status_.begin(),
|
runtime->privacy_sessions_.Connected(
|
||||||
runtime->connection_status_.end(), [](const auto& entry) {
|
remote_id, runtime->config_center_->IsEnablePrivacyScreen());
|
||||||
return entry.second == ConnectionStatus::Connected;
|
if (!runtime->privacy_sessions_.CanEnable() &&
|
||||||
})) {
|
runtime->privacy_.Engaged()) {
|
||||||
// Request optional privacy without delaying connection or capture.
|
runtime->privacy_.Disable();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
runtime->connection_status_[remote_id] = status;
|
runtime->connection_status_[remote_id] = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -428,7 +425,8 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
runtime->last_windows_service_status_tick_ = 0;
|
runtime->last_windows_service_status_tick_ = 0;
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
RemoteAction remote_action;
|
RemoteAction remote_action{};
|
||||||
|
remote_action.i.supports_privacy_screen = true;
|
||||||
remote_action.i.display_num =
|
remote_action.i.display_num =
|
||||||
runtime->devices_.display_info_list().size();
|
runtime->devices_.display_info_list().size();
|
||||||
remote_action.i.display_list =
|
remote_action.i.display_list =
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
@@ -20,9 +21,29 @@ bool GuiRuntime::IsAuthorizedController(const std::string& remote_id) {
|
|||||||
found->second == ConnectionStatus::Connected;
|
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,
|
void GuiRuntime::QueuePrivacyCommand(const std::string& remote_id,
|
||||||
const PrivacyCommand& command) {
|
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_);
|
std::lock_guard lock(privacy_commands_mutex_);
|
||||||
if (privacy_commands_.size() < 32)
|
if (privacy_commands_.size() < 32)
|
||||||
privacy_commands_.emplace_back(remote_id, command);
|
privacy_commands_.emplace_back(remote_id, command);
|
||||||
@@ -35,15 +56,22 @@ void GuiRuntime::HandlePrivacy() {
|
|||||||
commands.swap(privacy_commands_);
|
commands.swap(privacy_commands_);
|
||||||
}
|
}
|
||||||
for (const auto& [remote_id, command] : 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) {
|
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) {
|
} else if (command.flag == PrivacyCommandFlag::disable) {
|
||||||
privacy_.Disable();
|
privacy_.Disable();
|
||||||
}
|
}
|
||||||
last_privacy_status_tick_ = 0;
|
last_privacy_status_tick_ = 0;
|
||||||
}
|
}
|
||||||
const auto status = privacy_.Snapshot();
|
auto status = privacy_.Snapshot();
|
||||||
const uint64_t now = SDL_GetTicks();
|
const uint64_t now = SDL_GetTicks();
|
||||||
if (!peer_ || (last_privacy_status_tick_ != 0 &&
|
if (!peer_ || (last_privacy_status_tick_ != 0 &&
|
||||||
status.revision == last_privacy_revision_ &&
|
status.revision == last_privacy_revision_ &&
|
||||||
@@ -53,7 +81,15 @@ void GuiRuntime::HandlePrivacy() {
|
|||||||
{
|
{
|
||||||
std::shared_lock lock(connection_status_mutex_);
|
std::shared_lock lock(connection_status_mutex_);
|
||||||
for (const auto& [id, state] : connection_status_)
|
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{};
|
RemoteAction action{};
|
||||||
action.type = ControlType::privacy_status;
|
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++]);
|
out.type = static_cast<ControlType>(data[offset++]);
|
||||||
|
|
||||||
if (out.type == ControlType::host_infomation) {
|
if (out.type == ControlType::host_infomation) {
|
||||||
|
out.i.supports_privacy_screen = false;
|
||||||
size_t name_len;
|
size_t name_len;
|
||||||
if (!read(&name_len, sizeof(size_t)) ||
|
if (!read(&name_len, sizeof(size_t)) ||
|
||||||
name_len >= sizeof(out.i.host_name)) {
|
name_len >= sizeof(out.i.host_name)) {
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ struct HostInfo {
|
|||||||
int* top;
|
int* top;
|
||||||
int* right;
|
int* right;
|
||||||
int* bottom;
|
int* bottom;
|
||||||
|
// Optional capability; absent in legacy desktop and web host information.
|
||||||
|
bool supports_privacy_screen;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ServiceStatus {
|
struct ServiceStatus {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ void ResetHostInfo(HostInfo& info) {
|
|||||||
info.top = nullptr;
|
info.top = nullptr;
|
||||||
info.right = nullptr;
|
info.right = nullptr;
|
||||||
info.bottom = nullptr;
|
info.bottom = nullptr;
|
||||||
|
info.supports_privacy_screen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AllocateHostDisplays(HostInfo& info, std::size_t count) {
|
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},
|
object["host_info"] = {{"host_name", action.i.host_name},
|
||||||
{"display_num", action.i.display_num},
|
{"display_num", action.i.display_num},
|
||||||
{"displays", displays}};
|
{"displays", displays},
|
||||||
|
{"supports_privacy_screen",
|
||||||
|
action.i.supports_privacy_screen}};
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ControlType::invalid:
|
case ControlType::invalid:
|
||||||
@@ -272,6 +275,11 @@ bool RemoteAction::FromJson(const std::string& json_string,
|
|||||||
ResetHostInfo(output.i);
|
ResetHostInfo(output.i);
|
||||||
owns_host_info = true;
|
owns_host_info = true;
|
||||||
const auto& host_info_object = object.at("host_info");
|
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 =
|
const std::string host_name =
|
||||||
host_info_object.at("host_name").get<std::string>();
|
host_info_object.at("host_name").get<std::string>();
|
||||||
std::strncpy(output.i.host_name, host_name.c_str(),
|
std::strncpy(output.i.host_name, host_name.c_str(),
|
||||||
|
|||||||
Reference in New Issue
Block a user