#pragma once #include #include #include #include #include #include #include #include #include #include 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 last_requested_mode; std::optional 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 layout_width; std::optional layout_height; }; struct runtime_callbacks_t { std::function create_or_reclaim; // Resolve platform capability after the stable output has been reclaimed. // Implementations may safely downgrade requested state such as HDR. std::function resolve_mode; std::function &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(const std::string &client_uuid, const mode_t &mode)> exact_target_has_current_mode_and_dxgi; std::function 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(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 &known_clients, const std::vector &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 nodes); std::vector 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 managed_client_identity_ids() const; std::vector protected_remote_monitor_client_ids() const; bool generic_virtual_display_cleanup_allowed() const; void set_plaintext_rtsp_warning_provider(std::function 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 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 &paired_clients) const; private: struct client_state_t { std::string label; std::optional normal_requested_mode; std::optional 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 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 compose_locked(std::vector &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 &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 plaintext_rtsp_warning_provider_; nlohmann::json layout_ { {"version", layout_version}, {"placements", nlohmann::json::object()} }; std::vector physical_baseline_; std::unordered_map clients_; std::uint64_t next_normal_game_token_ = 0; std::uint64_t next_placement_order_ = 0; }; coordinator_t &instance(); } // namespace remote_display_topology