server/src/remote_display_topology.h
Rama main · réplica de lectura del repositorio original.
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <nlohmann/json.hpp>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
namespace remote_display_topology {
inline constexpr unsigned int layout_version = 1;
inline constexpr std::size_t max_client_identities = 4;
// A geometry that never lands must not hold a stream hostage: after this many deferrals the
// topology is attempted with what the platform reports and the warning stands.
inline constexpr int max_shared_geometry_deferrals = 3;
// The stable id the Remote Monitor role derives its own virtual display from. One paired
// client can hold a game role and a monitor role at the same time, and each role owns a
// separate display, so the id the display identity is hashed from has to carry the role.
// Without the suffix both roles hash to the same GUID: the monitor's activation then finds
// the game session's display, decides it must be replaced, cannot release it while it is
// being captured, and fails closed with a 503 (3 Nov 2026, second screen on the same machine
// that was already streaming).
std::string remote_monitor_stable_id(const std::string &client_uuid);
/// The role suffix that separates a client's monitor display from its game display.
std::string_view remote_monitor_suffix();
enum class lifecycle_e { desired, leased, applying, ready, retryable, released };
struct mode_t {
int width = 1920;
int height = 1080;
int refresh_hz = 60;
bool hdr = false;
};
struct node_t {
std::string id;
std::string device_id;
std::string label;
// Pre-existing streamed outputs are already owned by another session.
// device_id is their exact topology identity; id remains the paired owner
// identity used by the layout model.
bool preexisting = false;
bool physical = false;
bool active = false;
bool primary = false;
int x = 0;
int y = 0;
mode_t configured_mode;
std::optional<mode_t> last_requested_mode;
std::optional<mode_t> current_mode;
// Desktop coordinates can use a logical footprint that differs from the
// capture mode (for example, a 3840x2160 KWin output at 200% scale occupies
// 1920x1080). Keep mode reporting native while composing in desktop units.
std::optional<int> layout_width;
std::optional<int> layout_height;
};
struct runtime_callbacks_t {
std::function<bool(const std::string &client_uuid, const std::string &client_label, const mode_t &mode)> create_or_reclaim;
// Resolve platform capability after the stable output has been reclaimed.
// Implementations may safely downgrade requested state such as HDR.
std::function<void(const std::string &client_uuid, mode_t &mode)> resolve_mode;
std::function<bool(const std::vector<node_t> &composed)> apply_composed_topology;
// The output is accepted only after this exact client identity has both the
// requested mode and a capture-enumerated DXGI name. Passing the mode here
// prevents a callback from treating a GUID/device-id lookup as readiness.
std::function<std::optional<std::string>(const std::string &client_uuid, const mode_t &mode)> exact_target_has_current_mode_and_dxgi;
std::function<bool(const std::string &client_uuid)> remove_owned_display;
// The mode the client's own output actually has, as the compositor publishes it - not the
// mode it was asked for. A requested mode is a claim: until this agrees, placing another
// role beside it uses coordinates the desktop does not have, and the compositor refuses the
// whole topology and keeps the old one. Unset means the platform cannot observe, and every
// deferral below is skipped so behaviour is unchanged.
// `monitor_role` selects which of the client's two outputs is meant: this fork gives the
// game role and the Remote Monitor role a connector each, so the anchor geometry of a
// client is not the same output as the monitor's own.
std::function<std::optional<mode_t>(const std::string &client_uuid, bool monitor_role)> observe_shared_mode;
};
struct activation_result_t {
bool accepted = false;
bool capture_ready = false;
std::string warning;
};
struct monitor_runtime_state_t {
bool accepted = false;
bool ready = false;
bool retryable = false;
std::string output;
std::string error;
bool hdr_enabled = false;
};
struct normal_game_reservation_t {
bool accepted = false;
bool newly_reserved = false;
std::uint64_t token = 0;
};
// Validates the persisted wire contract without mutating coordinator state.
bool layout_schema_valid(const nlohmann::json &layout, std::string &error);
nlohmann::json normalize_layout(const nlohmann::json &layout);
bool validate_layout(const nlohmann::json &layout, const std::vector<std::string> &known_clients, const std::vector<std::string> &known_physical_ids, std::string &error);
class coordinator_t {
public:
void set_runtime_callbacks(runtime_callbacks_t callbacks);
void set_layout(nlohmann::json layout);
void set_physical_baseline(std::vector<node_t> nodes);
std::vector<std::string> physical_node_ids() const;
// Managed ownership is independent of transport lifetime. A retryable or
// transport-less Remote Monitor remains protected until an explicit owner
// release, and a normal game may share the same stable client identity.
std::size_t managed_client_identity_count() const;
bool normal_game_release_pending() const;
std::vector<std::string> managed_client_identity_ids() const;
std::vector<std::string> protected_remote_monitor_client_ids() const;
bool generic_virtual_display_cleanup_allowed() const;
void set_plaintext_rtsp_warning_provider(std::function<std::string(const std::string &)> provider);
monitor_runtime_state_t activate_or_resume(const std::string &client_uuid, const std::string &label, mode_t mode, uint64_t generation);
monitor_runtime_state_t snapshot(const std::string &client_uuid, uint64_t generation) const;
bool is_ready(const std::string &client_uuid, uint64_t generation) const;
void explicit_release(const std::string &client_uuid, uint64_t generation, const std::string &reason);
void transport_lost(const std::string &client_uuid, uint64_t generation);
activation_result_t activate_remote_monitor(const std::string &client_uuid, const std::string &label, mode_t mode);
activation_result_t resume_remote_monitor(const std::string &client_uuid);
normal_game_reservation_t reserve_normal_game_identity(const std::string &client_uuid, const std::string &label, mode_t mode);
bool reapply_composed_topology();
// True only when the caller may retire the platform output. Capture or
// Remote Monitor ownership, and stale tokens, must preserve it.
bool rollback_normal_game_identity(const std::string &client_uuid, std::uint64_t token);
void release_normal_game_identity(const std::string &client_uuid, std::uint64_t token);
// Capture references outlive the app and retain its output through GPU
// teardown. Drop them after joining capture; finalize under the stream
// lifecycle gate so their destructors never perform topology mutations.
std::shared_ptr<void> retain_normal_game_capture(const std::string &client_uuid, std::uint64_t token);
void release_drained_normal_game_identities();
void release_all_normal_game_identities();
void note_lease_lost(const std::string &client_uuid);
// A Remote Monitor held for resume outlives its transport, but not the app it was showing.
// Once that app ends for good, an identity that is no longer ready has nothing left to
// resume into, so it is released here along with the display it owns.
void release_abandoned_remote_monitors(const std::string &reason);
void disconnect_monitor(const std::string &client_uuid);
void unpair_client(const std::string &client_uuid);
/** Drop runtime state, optionally preserving every platform display untouched. */
void shutdown(bool preserve_owned_displays = false);
nlohmann::json snapshot(const std::vector<nlohmann::json> &paired_clients) const;
private:
struct client_state_t {
std::string label;
std::optional<mode_t> normal_requested_mode;
std::optional<mode_t> monitor_requested_mode;
// Platform resolution is applied to a copy. Desired per-role modes remain
// intact so a transient capability miss can recover and ending a Remote
// Monitor restores the normal game's mode.
mode_t effective_mode;
bool normal_game = false;
std::uint64_t normal_game_token = 0;
bool normal_release_pending = false;
std::unordered_map<std::uint64_t, std::size_t> normal_capture_references;
bool remote_monitor = false;
bool lease_held = false;
// A topology was composed from geometry this role only asked for. No live peer may be
// moved until observe_shared_mode agrees, because a refusal here retains the old
// topology and reads exactly like a compositor fault.
bool shared_mode_unverified = false;
int shared_mode_deferrals = 0;
uint64_t generation = 0;
// Stable first-ownership order keeps existing displays in place when a
// new owner has no saved layout. UUID ordering must not reshuffle the
// desktop merely because the newer UUID sorts first.
std::uint64_t placement_order = 0;
std::string exact_output;
lifecycle_e lifecycle = lifecycle_e::desired;
std::string warning;
};
activation_result_t activate_locked(const std::string &client_uuid, client_state_t &state);
struct capture_reference_t;
void release_normal_game_identity_locked(const std::string &client_uuid, client_state_t &state);
void release_locked(const std::string &client_uuid, client_state_t &state, const std::string &reason);
static mode_t desired_mode(const client_state_t &state);
void resolve_effective_mode_locked(const std::string &client_uuid, client_state_t &state);
// Adopt observed geometry for every role marked unverified except `except`. Returns false
// when an output could not be read, so the caller can defer instead of composing a guess.
bool refresh_unverified_shared_geometry_locked(const std::string &except = {});
std::vector<node_t> compose_locked(std::vector<std::string> &warnings) const;
// An empty composition is the correct end state when no role wants a display, not a refused
// apply. The composition callback refuses to build a topology from zero nodes on purpose, so
// treating that refusal as failure made the last release roll back and keep its identity
// owned for the life of the process.
bool apply_composed_or_accept_empty_locked(std::vector<std::string> &warnings);
static mode_t effective_mode(const node_t &node);
static int layout_width(const node_t &node);
static int layout_height(const node_t &node);
mutable std::mutex mutex_;
runtime_callbacks_t callbacks_;
std::function<std::string(const std::string &)> plaintext_rtsp_warning_provider_;
nlohmann::json layout_ { {"version", layout_version}, {"placements", nlohmann::json::object()} };
std::vector<node_t> physical_baseline_;
std::unordered_map<std::string, client_state_t> clients_;
std::uint64_t next_normal_game_token_ = 0;
std::uint64_t next_placement_order_ = 0;
};
coordinator_t &instance();
} // namespace remote_display_topology