[feat] unify Windows unattended hosting and device identity

This commit is contained in:
dijunkun committed 2026-11-10 10:43:43 +08:00
1 parent 930ccd9ea1
commit 4e2532f33c
67 files changed
+3491 -941

No files matched your search

@@ -79,7 +79,7 @@ void GuiRuntime::HandleConnectionStatusChange() {
const auto message =
BuildDevicePresenceRequest(client_id_, recent_connection_ids_, true, {},
gallery_presence_ids_).dump();
if (MiniRtcSendSignalMessage(peer_, message.data(), message.size()) == 0) {
if (PeerSendSignalMessage(peer_, message.data(), message.size()) == 0) {
next_presence_refresh_at_ = now + kPresenceRefreshInterval;
} else {
need_to_send_recent_connections_ = true;
@@ -200,7 +200,8 @@ void GuiRuntime::HandleServerControllerDisconnected(
std::unique_lock lock(connection_status_mutex_);
connection_status_.erase(remote_id);
disconnect_lock_.Disconnected(remote_id,
config_center_->IsLockOnDisconnect());
config_center_->IsLockOnDisconnect() &&
!(managed_host_frontend_ && remote_id.rfind("lan/", 0) != 0));
remote_input_.Disconnect(remote_id);
devices_.OnControllerConnectionsChanged();
connection_host_names_.erase(remote_id);
@@ -296,7 +297,7 @@ int GuiRuntime::RequestSingleDevicePresence(const std::string& remote_id,
gallery_presence_ids_)
.dump();
int ret = MiniRtcSendSignalMessage(peer_, s.data(), s.size());
int ret = PeerSendSignalMessage(peer_, s.data(), s.size());
if (ret != 0) {
std::lock_guard<std::mutex> lock(pending_presence_probe_mutex_);
pending_presence_probe_ = false;
@@ -315,7 +316,7 @@ int GuiRuntime::ForceSignalReconnect() {
LOG_WARN("Signal reconnect requested with no connection peer");
return -1;
}
return MiniRtcReconnectSignal(peer_);
return PeerReconnectSignal(peer_);
}
void GuiRuntime::CloseRemoteSession(std::shared_ptr<RemoteSession> props) {
@@ -430,7 +431,7 @@ void GuiRuntime::CloseServerController(const std::string& remote_id) {
return;
controller_cleanup_tasks_[remote_id] = session_cleanup_queue_.PostTask(
[peer = peer_, transmission_id = std::string(client_id_), remote_id] {
if (MiniRtcDisconnectPeerConnection(peer, transmission_id.c_str(),
if (PeerDisconnectPeerConnection(peer, transmission_id.c_str(),
remote_id.c_str()) != 0) {
LOG_WARN("[{}] Could not queue controller disconnect notification",
remote_id);
@@ -460,6 +461,14 @@ void GuiRuntime::CloseConnectionPeer() {
}
for (const auto& id : controllers)
HandleServerControllerDisconnected(id, "peer_closed");
#ifdef _WIN32
{
std::lock_guard lock(local_host_state_mutex_);
mirrored_controllers_.clear();
local_controller_info_.clear();
local_host_status_.controllers_dirty = true;
}
#endif
signal_connected_ = false;
device_presence_cache_.SetSignalConnected(false);
signal_status_ = MiniRtcSignalStatus::MiniRtcSignalClosed;
@@ -474,7 +483,8 @@ void GuiRuntime::CloseConnectionPeer() {
[peer, handler = peer_events_, id = std::string(client_id_)]() mutable {
handler->Deactivate();
LOG_INFO("[{}] Background connection peer cleanup", id);
MiniRtcLeaveConnection(peer, id.c_str());
if (!IsLocalPeer(peer)) MiniRtcLeaveConnection(peer, id.c_str());
UnbindLocalPeer(peer);
MiniRtcDestroyPeer(&peer);
});
}
@@ -14,6 +14,12 @@ void GuiRuntime::InitializeTrustedDevices(const std::filesystem::path& directory
}
bool GuiRuntime::TrustedControllerAllowed(const std::string& id) {
#ifdef _WIN32
if (managed_host_frontend_ && id.rfind("lan/", 0) != 0) {
std::lock_guard lock(local_host_state_mutex_);
return mirrored_controllers_.contains(id);
}
#endif
if (!config_center_ || !config_center_->IsTrustedDevicesOnly()) return true;
std::lock_guard lock(controller_trust_mutex_);
const auto it = controller_trust_.find(id);
@@ -21,6 +27,7 @@ bool GuiRuntime::TrustedControllerAllowed(const std::string& id) {
}
bool GuiRuntime::BeginControllerTrust(const std::string& id, PeerEventHandler* events) {
if (managed_host_frontend_ && !events->IsDirect()) return TrustedControllerAllowed(id);
const bool required = config_center_->IsTrustedDevicesOnly();
std::lock_guard lock(controller_trust_mutex_);
const auto previous = controller_trust_.find(id);
@@ -54,7 +61,7 @@ void GuiRuntime::ForgetControllerTrust(const std::string& id, PeerEventHandler*
int GuiRuntime::SendTrustMessage(const std::string& id, const nlohmann::json& message) {
const auto text = message.dump();
return WithController(id, [&](MiniRtcPeerPtr* peer, const std::string& wire) {
return MiniRtcSendReliableDataFrameToPeer(peer, text.data(), text.size(),
return PeerSendReliableDataFrameToPeer(peer, text.data(), text.size(),
control_data_label_.c_str(), wire.data(), wire.size());
}, false);
}
@@ -94,7 +101,7 @@ bool GuiRuntime::HandleDeviceTrustMessage(const std::string& id, PeerEventHandle
const auto wire = events->WireId(id);
const auto proof = trusted_devices_->Prove(message.at("nonce"),
props->local_id_, GetHostName()).dump();
MiniRtcSendReliableDataFrameToPeer(props->peer_, proof.data(), proof.size(),
PeerSendReliableDataFrameToPeer(props->peer_, proof.data(), proof.size(),
control_data_label_.c_str(), wire.data(), wire.size());
} else if (type == "device_trust_proof") {
std::lock_guard lock(controller_trust_mutex_);
@@ -127,6 +134,7 @@ void GuiRuntime::TickControllerTrust() {
{
std::lock_guard lock(controller_trust_mutex_);
for (auto& [id, state] : controller_trust_) {
if (managed_host_frontend_ && state.events == peer_events_.get()) continue;
if (state.closing) continue;
const bool trusted = state.IsTrusted(trusted_devices_.get());
if (required && (state.invalid || (state.verified && !trusted) ||
@@ -179,14 +187,28 @@ bool GuiRuntime::TrustController(const std::string& id, const std::string& expec
if (!trusted_devices_ || !IsAuthorizedController(id)) return false;
const auto device = VerifiedController(id);
if (!device || device->public_key != expected_key) return false;
try { trusted_devices_->Add(*device); return true; }
return AddTrustedDevice(*device);
}
bool GuiRuntime::AddTrustedDevice(const TrustedDevices::Device& device) {
#ifdef _WIN32
if (managed_host_frontend_) return SendLocalHostCommand({{"op", "trust_add"},
{"key", device.public_key}, {"name", device.name}, {"device", device.device_id}});
#endif
if (!trusted_devices_) return false;
try { trusted_devices_->Add(device); }
catch (const std::exception& error) { LOG_ERROR("Cannot trust device: {}", error.what()); return false; }
// Local persistence completes before publishing. A shared-store failure
// must not leave the in-memory trust list behind its own on-disk state.
return PublishSharedIdentity();
}
bool GuiRuntime::RemoveTrustedDevice(const std::string& public_key) {
#ifdef _WIN32
if (managed_host_frontend_) return SendLocalHostCommand({{"op", "trust_remove"}, {"key", public_key}});
#endif
if (!trusted_devices_) return false;
try { trusted_devices_->Remove(public_key); }
catch (const std::exception& error) { LOG_ERROR("Cannot remove trusted device: {}", error.what()); return false; }
TickControllerTrust();
return true;
return PublishSharedIdentity();
}
} // namespace crossdesk
+86 -2
View File
@@ -20,6 +20,8 @@
#include "rd_log.h"
#ifdef _WIN32
#include "platform/windows/input/windows_input_injector.h"
#include "platform/windows/service/autostart_control.h"
#include "service_host.h"
#endif
#if defined(__linux__) && !defined(__OHOS__)
#include "platform/linux/headless/headless_console.h"
@@ -131,6 +133,63 @@ void GuiRuntime::SendRemoteInput(const RemoteAction& action) {
}
}
bool GuiRuntime::PrepareSharedGuiIdentity(bool import_existing) {
#ifdef _WIN32
if (!SupportsServiceAutostart() || shared_identity_) return true;
if (!import_existing && settings_.Save() != 0) return false;
if (peer_) {
CloseConnectionPeer();
WaitForSessionCleanup();
}
std::string error;
if (!import_existing && !SharedHostIdentity::Prepare(
path_manager_->GetCachePath(), path_manager_->GetConfigPath() / "trusted-devices",
config_center_->ExportSharedHostConfig(), error)) {
LOG_ERROR("Cannot prepare persistent host: {}", error);
return false;
}
auto identity = SharedHostIdentity::AttachGui(path_manager_->GetCachePath(),
path_manager_->GetConfigPath() / "trusted-devices", import_existing, error);
if (!identity ||
(import_existing && config_center_->ApplySharedHostConfig(identity->HostConfig()) != 0)) {
LOG_ERROR("Cannot load the machine identity: {}", error);
return false;
}
shared_identity_ = std::move(identity);
managed_host_frontend_ = true;
StartCrossDeskService();
#else
(void)import_existing;
#endif
return true;
}
bool GuiRuntime::PublishSharedIdentity(bool include_config) {
#ifdef _WIN32
if (managed_host_frontend_) {
if (!include_config) return true;
pending_local_config_ =
{{"op", "config"}, {"config", config_center_->ExportSharedHostConfig()},
{"privacy_on_connect", config_center_->IsEnablePrivacyScreen()},
{"request", ++next_local_config_request_}};
local_config_sent_ = false;
return true; // Tick sends after startup/reattachment; errors return to the UI.
}
if (shared_identity_) {
local_host_profile_dirty_ = true;
std::string error;
if (!shared_identity_->Publish(include_config ? config_center_->ExportSharedHostConfig()
: std::string{}, error)) {
LOG_ERROR("Cannot synchronize the shared device identity: {}", error);
return false;
}
}
#else
(void)include_config;
#endif
return true;
}
int GuiRuntime::CreateConnectionPeer() {
// Tick retries after the old peer's leave notification and socket shutdown.
if (connection_peer_cleanup_.valid()) return 0;
@@ -286,12 +345,38 @@ int GuiRuntime::CreateConnectionPeer() {
need_to_send_recent_connections_ = true;
signal_status_ = MiniRtcSignalStatus::MiniRtcSignalConnecting;
// Complete slow headless display setup before connecting to signaling.
if (unattended_host_ && devices_.EnsureCaptureReady() != 0) return -1;
{
std::unique_lock lock(remote_sessions_mutex_);
peer_ = MiniRtcCreatePeer(&params_);
}
if (peer_) {
LOG_INFO("Create peer instance [{}] successful", client_id_);
#ifdef _WIN32
if (managed_host_frontend_) {
if (!local_host_) local_host_ = std::make_shared<LocalHostChannel>(false);
std::weak_ptr<LocalHostChannel> channel = local_host_;
BindLocalPeer(peer_, [this, channel](nlohmann::json message) {
if (message.contains("id")) {
std::lock_guard lock(local_host_state_mutex_);
const auto found = mirrored_controllers_.find(message.at("id").get<std::string>());
if (found == mirrored_controllers_.end()) return false;
message["epoch"] = found->second;
}
const auto connection = channel.lock();
return connection && connection->Send(message);
});
local_host_->Send({{"op", "hello"},
{"privacy_on_connect", config_center_->IsEnablePrivacyScreen()},
{"privacy_requested", privacy_.Requested() || config_center_->IsEnablePrivacyScreen()}});
// Outgoing viewer/direct sessions still register their native streams
// from the user's local display metadata; this does not start capture.
devices_.EnsureCaptureReady();
LOG_INFO("GUI attached to persistent host; no second signaling login");
return 0;
}
#endif
MiniRtcInit(peer_);
LOG_INFO("Peer [{}] init finish", client_id_);
} else {
@@ -313,8 +398,7 @@ int GuiRuntime::CreateConnectionPeer() {
MiniRtcAddDataStream(peer_, file_label_.c_str(), true);
MiniRtcAddDataStream(peer_, file_feedback_label_.c_str(), true);
MiniRtcAddDataStream(peer_, clipboard_label_.c_str(), true);
if (!unattended_host_)
MiniRtcAddDataStream(peer_, web_clipboard::kStream, true);
MiniRtcAddDataStream(peer_, web_clipboard::kStream, true);
return 0;
} else {
return -1;
@@ -27,11 +27,16 @@
#include "runtime/disconnect_lock_policy.h"
#include "runtime/announcement_inbox.h"
#include "runtime/peer_event_handler.h"
#include "runtime/peer_transport.h"
#include "runtime/privacy_session_policy.h"
#include "runtime/trusted_devices.h"
#ifdef __APPLE__
#include "platform/macos/gui/runtime/mac_permission_migration.h"
#endif
#ifdef _WIN32
#include "local_host_channel.h"
#include "runtime/local_host_status.h"
#endif
namespace crossdesk {
@@ -114,6 +119,8 @@ class GuiRuntime : protected gui_detail::GuiState {
void HandleScreenLock();
bool IsAuthorizedController(const std::string& remote_id);
void InitializeTrustedDevices(const std::filesystem::path& directory);
bool PublishSharedIdentity(bool include_config = false);
bool PrepareSharedGuiIdentity(bool import_existing);
bool TrustedControllerAllowed(const std::string& remote_id);
bool BeginControllerTrust(const std::string& remote_id, PeerEventHandler* events);
void ForgetControllerTrust(const std::string& remote_id, PeerEventHandler* events);
@@ -122,6 +129,7 @@ class GuiRuntime : protected gui_detail::GuiState {
void TickControllerTrust();
std::optional<TrustedDevices::Device> VerifiedController(const std::string& remote_id);
bool TrustController(const std::string& remote_id, const std::string& expected_key);
bool AddTrustedDevice(const TrustedDevices::Device& device);
bool RemoveTrustedDevice(const std::string& public_key);
int SendTrustMessage(const std::string& remote_id, const nlohmann::json& message);
std::unique_ptr<TrustedDevices> trusted_devices_;
@@ -201,6 +209,39 @@ class GuiRuntime : protected gui_detail::GuiState {
PrivacyController privacy_;
// Set before initializing callbacks; immutable for the process lifetime.
bool unattended_host_ = false;
bool managed_host_frontend_ = false;
std::string BuildHostInformation(bool advertise_trust = false);
void AnnounceHostCapabilities(const std::string& id);
#ifdef _WIN32
std::shared_ptr<LocalHostChannel> local_host_;
std::atomic<bool> local_gui_ready_{false};
std::mutex local_host_state_mutex_;
std::map<std::string, nlohmann::json> local_controller_info_;
std::map<std::string, uint64_t> mirrored_controllers_;
uint64_t next_controller_epoch_ = 0;
uint64_t next_local_host_status_ = 0;
LocalHostStatus local_host_status_;
std::atomic<bool> local_host_profile_dirty_{true};
nlohmann::json pending_local_config_;
uint64_t next_local_config_request_ = 0;
bool local_config_sent_ = false;
std::optional<bool> local_cursor_hidden_sent_;
std::optional<bool> local_cursor_request_sent_;
bool local_cursor_policy_ready_ = false;
bool host_cursor_default_ = true, host_cursor_preference_ = true;
std::atomic<uint64_t> host_capture_tick_{0};
bool ForwardUserSessionData(const std::string& id, const std::string& stream,
const char* data, size_t size);
void PublishLocalHostStatus();
bool SendLocalHostCommand(nlohmann::json message);
#endif
bool UserSessionFeaturesAvailable() const {
#ifdef _WIN32
return !unattended_host_ || local_gui_ready_.load();
#else
return !unattended_host_;
#endif
}
AnnouncementInbox announcements_;
PrivacySessionPolicy privacy_sessions_;
DisconnectLockPolicy disconnect_lock_;
+1 -1
View File
@@ -199,7 +199,7 @@ int GuiRuntime::WithController(
int GuiRuntime::SendControllerData(const std::string& id, const char* data,
size_t size, const char* label) {
return WithController(id, [&](MiniRtcPeerPtr* peer, const std::string& wire) {
return MiniRtcSendReliableDataFrameToPeer(peer, data, size, label, wire.data(),
return PeerSendReliableDataFrameToPeer(peer, data, size, label, wire.data(),
wire.size());
});
}
@@ -0,0 +1,43 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-11-09
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _LOCAL_HOST_STATUS_H_
#define _LOCAL_HOST_STATUS_H_
#include <nlohmann/json.hpp>
namespace crossdesk {
// Pending sections survive deferred settings and failed local persistence.
// Clear each flag only after its section has been applied successfully.
struct LocalHostStatus {
nlohmann::json values = nlohmann::json::object();
bool config_dirty = true;
bool identity_dirty = true;
bool controllers_dirty = true;
bool trusted_dirty = true;
void Reset() {
values.clear();
config_dirty = identity_dirty = controllers_dirty = trusted_dirty = true;
}
void Merge(const nlohmann::json& update) {
const auto changed = [&](const char* field) {
return update.contains(field) &&
(!values.contains(field) || update.at(field) != values.at(field));
};
config_dirty |= changed("config");
identity_dirty |= changed("identity") || changed("self_hosted");
controllers_dirty |= changed("controllers");
trusted_dirty |= changed("trusted");
values.update(update);
}
};
} // namespace crossdesk
#endif
@@ -139,6 +139,10 @@ void PeerEventHandler::OnReceiveDataBuffer(
const auto viewer = runtime->FindRemoteSession(remote_id);
if (viewer && viewer->peer_events_.get() != handler) return;
if (!viewer && !runtime->TrustedControllerAllowed(remote_id)) return;
#ifdef _WIN32
if (runtime->unattended_host_ && !viewer &&
runtime->ForwardUserSessionData(remote_id, source_id, data, size)) return;
#endif
// The service-owned host runs as SYSTEM. Do not expose filesystem or
// clipboard operations in that security context.
if (runtime->unattended_host_ &&
@@ -198,6 +202,11 @@ void PeerEventHandler::OnReceiveDataBuffer(
return;
const auto subscription = PreviewSubscription::Parse(preview_json);
if (!subscription) return;
if (runtime->managed_host_frontend_ && !handler->IsDirect()) {
runtime->preview_subscriptions_.Set(remote_id, *subscription);
if (subscription->audio) runtime->devices_.StartSpeakerCapturer();
return; // The persistent host has already applied/acknowledged this.
}
const auto previous = runtime->preview_subscriptions_.Get(remote_id);
bool accepted = true;
// Apply encoder quality to this controller only; other controllers retain
@@ -240,7 +249,7 @@ void PeerEventHandler::OnReceiveDataBuffer(
{"request_id", subscription->request_id},
{"accepted",
accepted}}.dump();
MiniRtcSendReliableDataFrameToPeer(bound_peer, reply.data(), reply.size(),
PeerSendReliableDataFrameToPeer(bound_peer, reply.data(), reply.size(),
runtime->control_data_label_.c_str(),
wire_id.data(), wire_id.size());
return;
@@ -324,7 +333,7 @@ void PeerEventHandler::OnReceiveDataBuffer(
settings.frame_rate, settings.preference, remote_action.vs.accepted);
remote_action.type = ControlType::video_settings_status;
const auto message = remote_action.to_json();
MiniRtcSendReliableDataFrameToPeer(bound_peer, message.data(), message.size(),
PeerSendReliableDataFrameToPeer(bound_peer, message.data(), message.size(),
runtime->control_data_label_.c_str(),
wire_id.data(), wire_id.size());
return;
@@ -526,6 +535,10 @@ void PeerEventHandler::OnReceiveDataBuffer(
}
} else {
if (remote_action.type == ControlType::audio_capture) {
if (runtime->managed_host_frontend_ && !handler->IsDirect()) {
if (remote_action.a) runtime->devices_.StartSpeakerCapturer();
return;
}
// A negotiated subscription remains authoritative if a legacy capture
// command arrives after it (e.g. when unmuting an inactive window).
const auto subscription = runtime->preview_subscriptions_.Get(remote_id);
@@ -586,8 +599,10 @@ InputOutcome GuiRuntime::ApplyRemoteInput(const std::string& remote_id,
const int display = selected_display_.load();
if (is_server_mode_ && remote_action.type == ControlType::mouse) {
std::lock_guard lock(remote_pointer_input_mutex_);
last_remote_pointer_input_time_[remote_id] =
std::chrono::steady_clock::now();
const auto now = std::chrono::steady_clock::now();
auto [position, first] = last_remote_pointer_input_time_.try_emplace(remote_id, now);
position->second = now;
if (first) LOG_INFO("Remote pointer input received: peer={}", remote_id);
}
if (remote_action.type == ControlType::mouse) {
@@ -652,6 +667,12 @@ InputOutcome GuiRuntime::InjectRemoteMouse(const RemoteAction& remote_action,
static_cast<int>(remote_action.m.flag));
auto json = nlohmann::json::parse(response, nullptr, false);
const bool sent = json.is_object() && json.value("ok", false);
static thread_local bool logged_service_input = false;
if (sent && !logged_service_input) {
logged_service_input = true;
LOG_INFO("Secure desktop pointer injection succeeded: stage='{}', desktop='{}'",
WindowsInputStage(), json.value("desktop", ""));
}
const std::string error =
json.is_object() ? json.value("error", "") : "";
return {sent, IsDesktopInputSetupPending(error)};
@@ -75,7 +75,7 @@ void PeerEventHandler::SendClientInfo(MiniRtcPeerPtr* peer,
{"version", CROSSDESK_VERSION},
{"platform", HostPlatformName(kClientPlatform)}};
const std::string payload = message.dump();
if (MiniRtcSendSignalMessage(peer, payload.data(), payload.size()) != 0) {
if (PeerSendSignalMessage(peer, payload.data(), payload.size()) != 0) {
LOG_WARN("[{}] failed to report client information", client_id);
}
}
@@ -118,6 +118,12 @@ void PeerEventHandler::OnSignalMessage(const char* message, size_t size,
return;
}
std::string type = j["type"].get<std::string>();
#ifdef _WIN32
if (runtime->unattended_host_ && runtime->local_host_ &&
(type == "presence" || type == "presence_update" ||
type == "announcements" || type == "announcements_changed"))
runtime->local_host_->Send({{"op", "signal"}, {"message", s}});
#endif
if (type == "user_join_transmission" && j.contains("status") &&
j["status"] == "failed" && j.contains("transmission_id") &&
j["transmission_id"].is_string()) {
@@ -329,6 +335,19 @@ void PeerEventHandler::DispatchConnectionStatus(MiniRtcConnectionStatus status,
} else {
runtime->ForgetControllerTrust(remote_id, handler);
}
#ifdef _WIN32
if (runtime->unattended_host_) {
std::lock_guard lock(runtime->local_host_state_mutex_);
if (status == MiniRtcConnectionStatus::MiniRtcConnectionConnected) {
auto& info = runtime->local_controller_info_[remote_id];
if (!info.contains("epoch")) info["epoch"] = ++runtime->next_controller_epoch_;
} else if (status == MiniRtcConnectionStatus::MiniRtcConnectionClosed ||
status == MiniRtcConnectionStatus::MiniRtcConnectionFailed ||
status == MiniRtcConnectionStatus::MiniRtcConnectionDisconnected) {
runtime->local_controller_info_.erase(remote_id);
}
}
#endif
}
if (props) {
@@ -348,25 +367,7 @@ void PeerEventHandler::DispatchConnectionStatus(MiniRtcConnectionStatus status,
props->cursor_presentation_ = {};
}
{
RemoteAction remote_action = MakeHostInformation(
GetHostName(), runtime->devices_.host_display_list(),
/*supports_privacy_screen=*/!runtime->unattended_host_,
CROSSDESK_VERSION, kClientPlatform);
remote_action.i.supports_video_settings = true;
remote_action.i.supports_video_codec_status = true;
remote_action.i.supports_text_input = runtime->devices_.SupportsTextInput();
remote_action.i.supports_deskhub_preview = true;
remote_action.i.supports_deskhub_resolution = true;
remote_action.i.supports_deskhub_fps15 = true;
remote_action.i.supports_deskhub_bandwidth = true;
remote_action.i.supports_file_browser = !runtime->unattended_host_;
for (std::size_t i = 0; i < remote_action.i.display_num; i++) {
LOG_INFO("Local display [{}:{}]", i + 1,
remote_action.i.display_list[i]);
}
auto information = nlohmann::json::parse(remote_action.to_json());
information["device_trust_version"] = 1;
std::string msg = information.dump();
const auto msg = runtime->BuildHostInformation(true);
const int ret =
MiniRtcSendReliableDataFrame(props->peer_, msg.data(), msg.size(),
runtime->control_data_label_.c_str());
@@ -374,7 +375,6 @@ void PeerEventHandler::DispatchConnectionStatus(MiniRtcConnectionStatus status,
LOG_WARN("[{}] host information send failed, ret={}", remote_id,
ret);
}
FreeRemoteAction(remote_action);
}
if (!props->wall_managed_ && !runtime->need_to_create_stream_window_) {
@@ -517,43 +517,20 @@ void PeerEventHandler::DispatchConnectionStatus(MiniRtcConnectionStatus status,
#if _WIN32
runtime->last_windows_service_status_tick_ = 0;
#endif
{
RemoteAction remote_action = MakeHostInformation(
GetHostName(), runtime->devices_.host_display_list(),
/*supports_privacy_screen=*/!runtime->unattended_host_,
CROSSDESK_VERSION, kClientPlatform);
remote_action.i.supports_video_settings = true;
remote_action.i.supports_video_codec_status = true;
remote_action.i.supports_text_input = runtime->devices_.SupportsTextInput();
remote_action.i.supports_deskhub_preview = true;
remote_action.i.supports_deskhub_resolution = true;
remote_action.i.supports_deskhub_fps15 = true;
remote_action.i.supports_deskhub_bandwidth = true;
remote_action.i.supports_file_browser = !runtime->unattended_host_;
for (std::size_t i = 0; i < remote_action.i.display_num; i++) {
LOG_INFO("Local display [{}:{}]", i + 1,
remote_action.i.display_list[i]);
}
std::string msg = remote_action.to_json();
const int ret =
runtime->SendControllerData(remote_id, msg.data(), msg.size(),
runtime->control_data_label_.c_str());
if (ret != 0) {
LOG_WARN("[{}] host information send failed, ret={}", remote_id,
ret);
}
FreeRemoteAction(remote_action);
}
if (!runtime->managed_host_frontend_ || handler->IsDirect())
runtime->AnnounceHostCapabilities(remote_id);
runtime->need_to_destroy_server_window_.store(
false, std::memory_order_release);
runtime->need_to_create_server_window_.store(true,
std::memory_order_release);
runtime->is_server_mode_ = true;
runtime->start_screen_capturer_ = true;
if (!runtime->managed_host_frontend_ || handler->IsDirect())
runtime->start_screen_capturer_ = true;
runtime->devices_.StartSpeakerCapturer();
runtime->remote_client_id_ = remote_id;
runtime->start_mouse_controller_ = true;
if (!runtime->managed_host_frontend_ || handler->IsDirect())
runtime->start_mouse_controller_ = true;
break;
}
@@ -670,4 +647,33 @@ void PeerEventHandler::OnNetStatusReport(
props->net_traffic_stats_.Store(*net_traffic_stats);
}
}
std::string GuiRuntime::BuildHostInformation(bool advertise_trust) {
auto action = MakeHostInformation(GetHostName(), devices_.host_display_list(),
UserSessionFeaturesAvailable(), CROSSDESK_VERSION, kClientPlatform);
action.i.supports_video_settings = true;
action.i.supports_video_codec_status = true;
action.i.supports_text_input = devices_.SupportsTextInput();
action.i.supports_deskhub_preview = true;
action.i.supports_deskhub_resolution = true;
action.i.supports_deskhub_fps15 = true;
action.i.supports_deskhub_bandwidth = true;
action.i.supports_file_browser = UserSessionFeaturesAvailable();
for (std::size_t i = 0; i < action.i.display_num; ++i)
LOG_INFO("Local display [{}:{}]", i + 1, action.i.display_list[i]);
auto message = action.to_json();
FreeRemoteAction(action);
if (advertise_trust) {
auto information = nlohmann::json::parse(message);
information["device_trust_version"] = 1;
message = information.dump();
}
return message;
}
void GuiRuntime::AnnounceHostCapabilities(const std::string& id) {
const auto message = BuildHostInformation();
const int ret = SendControllerData(id, message.data(), message.size(), control_data_label_.c_str());
if (ret != 0) LOG_WARN("[{}] host information send failed, ret={}", id, ret);
}
} // namespace crossdesk
@@ -0,0 +1,104 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-11-09
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _PEER_TRANSPORT_H_
#define _PEER_TRANSPORT_H_
#include "runtime/peer_transport.h"
#ifdef _WIN32
#include <map>
#include <mutex>
#endif
namespace crossdesk {
namespace {
using Json = nlohmann::json;
using LocalSender = std::function<bool(Json)>;
Json Bytes(const char* data, size_t size) {
return Json::binary(std::vector<uint8_t>(data, data + size));
}
#ifdef _WIN32
std::mutex routes_mutex;
std::map<MiniRtcPeerPtr*, LocalSender> routes;
LocalSender FindLocalSender(MiniRtcPeerPtr* peer) {
std::lock_guard lock(routes_mutex);
const auto found = routes.find(peer);
return found == routes.end() ? LocalSender{} : found->second;
}
#else
LocalSender FindLocalSender(MiniRtcPeerPtr*) { return {}; }
#endif
}
#ifdef _WIN32
void BindLocalPeer(MiniRtcPeerPtr* peer, std::function<bool(Json)> send) {
std::lock_guard lock(routes_mutex);
routes[peer] = std::move(send);
}
void UnbindLocalPeer(MiniRtcPeerPtr* peer) {
std::lock_guard lock(routes_mutex);
routes.erase(peer);
}
bool IsLocalPeer(MiniRtcPeerPtr* peer) {
std::lock_guard lock(routes_mutex);
return routes.contains(peer);
}
#else
void BindLocalPeer(MiniRtcPeerPtr*, std::function<bool(Json)>) {}
void UnbindLocalPeer(MiniRtcPeerPtr*) {}
bool IsLocalPeer(MiniRtcPeerPtr*) { return false; }
#endif
int PeerSendReliableDataFrameToPeer(MiniRtcPeerPtr* peer, const char* data,
size_t size, const char* stream, const char* id, size_t id_size) {
if (auto send = FindLocalSender(peer)) {
if (!data || !size || !stream || !id || !id_size) return -1;
return send({{"op", "data"}, {"id", std::string(id, id_size)},
{"stream", stream}, {"bytes", Bytes(data, size)}}) ? 0 : -1;
}
return MiniRtcSendReliableDataFrameToPeer(peer, data, size, stream, id, id_size);
}
int PeerSendAudioFrameToPeer(MiniRtcPeerPtr* peer, const MiniRtcAudioFrame* frame,
const char* stream, const char* id, size_t size) {
if (auto send = FindLocalSender(peer)) {
if (!frame || !frame->data || !frame->size || !stream || !id || !size) return -1;
return send({{"op", "audio"}, {"id", std::string(id, size)},
{"stream", stream}, {"bytes", Bytes(frame->data, frame->size)}}) ? 0 : -1;
}
return MiniRtcSendAudioFrameToPeer(peer, frame, stream, id, size);
}
int PeerSendVideoFrameToPeer(MiniRtcPeerPtr* peer, const MiniRtcVideoFrame* frame,
const char* stream, const char* id, size_t size) {
// The persistent host owns video. A GUI capturing for a direct LAN viewer
// must not send that second capture into its mirrored cloud connections.
return IsLocalPeer(peer) ? 0 : MiniRtcSendVideoFrameToPeer(peer, frame, stream, id, size);
}
int PeerSendSignalMessage(MiniRtcPeerPtr* peer, const char* data, size_t size) {
if (auto send = FindLocalSender(peer)) {
if (!data || !size) return -1;
return send({{"op", "signal"}, {"message", std::string(data, size)}}) ? 0 : -1;
}
return MiniRtcSendSignalMessage(peer, data, size);
}
int PeerReconnectSignal(MiniRtcPeerPtr* peer) {
if (auto send = FindLocalSender(peer)) return send({{"op", "reconnect"}}) ? 0 : -1;
return MiniRtcReconnectSignal(peer);
}
int PeerDisconnectPeerConnection(MiniRtcPeerPtr* peer, const char* host, const char* id) {
if (auto send = FindLocalSender(peer))
return send({{"op", "disconnect"}, {"id", id}}) ? 0 : -1;
return MiniRtcDisconnectPeerConnection(peer, host, id);
}
int PeerUpdateConnectionSettings(MiniRtcPeerPtr* peer, bool hardware, bool av1,
MiniRtcTurnMode turn) {
if (auto send = FindLocalSender(peer))
return send({{"op", "connection_settings"},
{"av1", av1}, {"turn", static_cast<int>(turn)}}) ? 0 : -1;
return MiniRtcUpdateConnectionSettings(peer, hardware, av1, turn);
}
} // namespace crossdesk
#endif
@@ -0,0 +1,34 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-11-09
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _PEER_TRANSPORT_H_
#define _PEER_TRANSPORT_H_
#include <minirtc.h>
#include <nlohmann/json.hpp>
#include <functional>
namespace crossdesk {
// A managed GUI has a local endpoint instead of another signaling login.
// Viewer peers and direct LAN peers continue to use MiniRTC normally.
void BindLocalPeer(MiniRtcPeerPtr*, std::function<bool(nlohmann::json)> send);
void UnbindLocalPeer(MiniRtcPeerPtr*);
bool IsLocalPeer(MiniRtcPeerPtr*);
int PeerSendReliableDataFrameToPeer(MiniRtcPeerPtr*, const char*, size_t,
const char*, const char*, size_t);
int PeerSendAudioFrameToPeer(MiniRtcPeerPtr*, const MiniRtcAudioFrame*,
const char*, const char*, size_t);
int PeerSendVideoFrameToPeer(MiniRtcPeerPtr*, const MiniRtcVideoFrame*,
const char*, const char*, size_t);
int PeerSendSignalMessage(MiniRtcPeerPtr*, const char*, size_t);
int PeerReconnectSignal(MiniRtcPeerPtr*);
int PeerDisconnectPeerConnection(MiniRtcPeerPtr*, const char*, const char*);
int PeerUpdateConnectionSettings(MiniRtcPeerPtr*, bool, bool, MiniRtcTurnMode);
} // namespace crossdesk
#endif
@@ -33,6 +33,7 @@
#if _WIN32
#include "platform/windows/service/service_query_worker.h"
#include "platform/windows/service/shared_identity.h"
#endif
namespace crossdesk::gui_detail {
@@ -47,6 +48,9 @@ struct InfrastructureState {
std::string exec_log_path_;
std::string dll_log_path_;
std::string cache_path_;
#ifdef _WIN32
std::unique_ptr<SharedHostIdentity> shared_identity_;
#endif
int localization_language_index_ = -1;
bool modules_inited_ = false;
unsigned char aes128_key_[16]{};
@@ -230,4 +230,16 @@ void TrustedDevices::Remove(const std::string& public_key) {
Save(next);
devices_.swap(next);
}
void TrustedDevices::Replace(const std::vector<Device>& devices) {
Require(devices.size() <= kMaxTrustedDevices);
std::map<std::string, Device> next;
for (const auto& device : devices) {
Require(IsHex(device.public_key, 32) && Label(device.name) && Label(device.device_id));
Require(next.emplace(device.public_key, device).second);
}
std::lock_guard lock(mutex_);
if (next == devices_) return;
Save(next);
devices_.swap(next);
}
} // namespace crossdesk
@@ -48,6 +48,9 @@ class TrustedDevices {
// Call only after a local add action for a verified peer or valid pairing info.
void Add(const Device& device);
void Remove(const std::string& public_key);
// Replace a complete snapshot from the authenticated machine host.
// Validation and persistence finish before the in-memory list changes.
void Replace(const std::vector<Device>& devices);
private:
void Save(const std::map<std::string, Device>& devices) const;