Ctrl K
ComunidadGuías…

server/src/remote_display_topology.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#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