use clap::{Parser, ValueEnum}; #[derive(Parser, Debug)] #[command( name = "pixelpass", version, about = "P2P screen sharing over iroh", long_about = "Run with no arguments for an interactive Host/View menu. \ Pass a ticket positionally to skip the menu and view headlessly." )] pub struct Cli { /// iroh ticket. If present, runs as viewer. If absent, runs as host. pub ticket: Option, // ── host options ────────────────────────────────────────────────── /// Run as host without the interactive menu. Equivalent to picking /// "Host" in the menu, but headless — for scripting and the --gui /// front-end, which drives this binary as a child process. #[arg(long)] pub host: bool, /// Pick a single window instead of the whole screen. #[arg(long)] pub window: bool, /// Capture only this app's audio (per-app PipeWire routing). #[arg(long, value_name = "NAME")] pub app: Option, /// With `--app`, never fall back to whole-desktop audio. By default an /// app-filtered host mirrors the default sink's monitor until (and again /// after) the chosen app's streams route, so the viewer isn't left in /// silence. That fallback also captures everything else playing — including /// a voice call the sharer is in — so a caller can hear themselves echoed. /// `--strict-audio` suppresses the fallback entirely: the viewer hears only /// the chosen app, and silence when it isn't producing audio. Ignored /// without `--app`. #[arg(long)] pub strict_audio: bool, /// Override display server autodetection. #[arg(long, value_enum)] pub display_server: Option, /// Quality preset. Bundles a max video height, bitrate, and framerate. /// `auto` derives them from the saved bandwidth pre-flight (falls back to /// `medium` when no measurement exists). Defaults to `auto`; in the /// interactive menu, omitting this shows a picker instead. #[arg(long, value_enum)] pub quality: Option, /// Cap the encoded video height (px); width follows the source aspect. /// Power-user override — takes precedence over the preset's height. #[arg(long, value_name = "N")] pub max_height: Option, /// Encode bitrate in kbps. Overrides the quality preset's bitrate. #[arg(long)] pub bitrate: Option, /// Capture framerate. Overrides the quality preset's framerate. #[arg(long)] pub framerate: Option, /// Disable VAAPI HW encode; force software x264. #[arg(long)] pub no_hwencode: bool, /// Maximum number of concurrent viewers. Additional connections are /// politely refused with a "host full" message. Defaults to the /// connection-aware recommendation from the bandwidth pre-flight if /// available, otherwise 2. #[arg(long)] pub max_viewers: Option, // ── viewer options ──────────────────────────────────────────────── /// Local TCP port for the viewer to expose (default: random). #[arg(long, default_value_t = 0)] pub port: u16, // ── global ──────────────────────────────────────────────────────── /// Relay server URL to use instead of the bundled defaults, e.g. /// `https://relay.example/`. Applies to both host and viewer. Falls back /// to the `PIXELPASS_RELAY` environment variable. Use this to get off the /// pre-release default relays or to point at a self-hosted relay. #[arg(long, value_name = "URL")] pub relay: Option, /// Launch the graphical front-end (a window with Host/View controls) /// instead of the terminal menu. Requires a build with `--features gui`. #[arg(long)] pub gui: bool, /// Emit machine-readable events on stdout (one JSON object per line) /// alongside the human banner on stderr. For scripts and the --gui /// front-end. Currently only `json` is supported. #[arg(long, value_enum, value_name = "FORMAT")] pub output: Option, /// Trace-level logging. #[arg(long, short)] pub verbose: bool, /// Clean up orphaned PipeWire state from a crashed host run, then exit. #[arg(long)] pub repair: bool, /// Print an environment diagnostic report (display server, capture/encode /// dependencies, VA-API H.264 support, viewer player, relay reachability), /// then exit. Use this to check a machine can host or view before a real /// session — especially to confirm hardware H.264 encode works, since a GPU /// without it silently produces no video under the default encoder. #[arg(long)] pub doctor: bool, /// Re-run the bandwidth pre-flight test, save the result, then exit. /// Use this if your connection has changed (new ISP, moved house, etc.) /// or if the previously saved test result is stale. #[arg(long)] pub reconfigure: bool, /// Run the read-only audio-exclusion dry-run audit against the live /// PipeWire graph, then exit on ctrl-c. Emits one JSON object per line to /// stderr (or to `PIXELPASS_AUDIO_AUDIT_FILE`) describing which audio /// streams would be eligible for a screen share and why the rest would not. /// Creates no links and changes no routing. /// /// Hidden: this is development instrumentation for the screen-share audio /// exclusion work (impl plan phase 5), not a user-facing feature, and the /// record schema is free to change until phase 6 fixes it. #[arg(long, hide = true)] pub audit_audio: bool, } #[derive(ValueEnum, Clone, Copy, Debug)] pub enum DisplayServerArg { Wayland, X11, } #[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] pub enum OutputFormat { /// One JSON object per line on stdout. Json, } /// Quality preset. Each fixed preset bundles a (max-height, bitrate, fps) /// tuple — resolution is a quality-per-bitrate knob, so the three only make /// sense together. `Auto` has no fixed tuple; it picks one of the others from /// the bandwidth pre-flight at host startup. See `host::quality`. #[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] pub enum Quality { /// Native source resolution, 6000 kbps, 30 fps (no downscale). Source, /// Up to 1080p, 4000 kbps, 30 fps. High, /// Up to 720p, 2500 kbps, 30 fps. Medium, /// Up to 480p, 1000 kbps, 30 fps. Low, /// Derive from the measured upstream; falls back to `medium` when unmeasured. Auto, } #[derive(Debug, Clone)] pub struct HostOpts { pub window: bool, pub app: Option, /// With `app` set, suppress the whole-desktop loopback fallback so the /// viewer only ever hears the chosen app (silence when it's quiet). No /// effect when `app` is None. pub strict_audio: bool, pub display_server: Option, /// Chosen preset (Auto = derive at startup). Defaults to Auto. pub quality: Quality, /// Raw `--bitrate` override (kbps); None = use the preset's bitrate. pub bitrate: Option, /// Raw `--framerate` override; None = use the preset's framerate. pub framerate: Option, /// Raw `--max-height` override (px); None = use the preset's height. pub max_height: Option, pub no_hwencode: bool, pub max_viewers: Option, pub interactive: bool, /// Relay override (resolved from `--relay` / `PIXELPASS_RELAY`); None = defaults. pub relay: Option, } #[derive(Debug, Clone)] pub struct ViewerOpts { pub port: u16, pub interactive: bool, /// Relay override (resolved from `--relay` / `PIXELPASS_RELAY`); None = defaults. pub relay: Option, } impl Cli { pub fn into_host_opts(self, interactive: bool) -> HostOpts { HostOpts { window: self.window, app: self.app, strict_audio: self.strict_audio, display_server: self.display_server, // No `--quality` and nothing picked interactively → the documented // default, Auto. quality: self.quality.unwrap_or(Quality::Auto), bitrate: self.bitrate, framerate: self.framerate, max_height: self.max_height, no_hwencode: self.no_hwencode, max_viewers: self.max_viewers, interactive, relay: crate::common::endpoint::relay_override(self.relay.as_deref()), } } pub fn into_viewer_opts(self, interactive: bool) -> ViewerOpts { ViewerOpts { port: self.port, interactive, relay: crate::common::endpoint::relay_override(self.relay.as_deref()), } } }