mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-13 20:25:53 +08:00
[feat] unify Windows unattended hosting and device identity
This commit is contained in:
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
|
||||
@@ -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(¶ms_);
|
||||
}
|
||||
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_;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user