Files
pixelpass/src/cli.rs
T
molluskandClaude Opus 4.8 eb077d81f0 feat(relay): add --relay / PIXELPASS_RELAY override
Both host and viewer hardcoded presets::N0, pinning every session to the
bundled relays (which on iroh rc.0 are the canary-grade defaults). Add a
shared common::endpoint::bind() that keeps N0's DNS discovery + crypto but
swaps in a RelayMode::Custom single-relay map when --relay (or the
PIXELPASS_RELAY env var, so GUI children inherit it) is set.

Lets users point at a self-hosted relay or staging today; the production
relays (*.relay.iroh.network) speak a newer protocol that rc.0 rejects
("invalid iroh-relay version header"), so they only become usable — and
the default — after an iroh GA bump. Verified: override connects cleanly
through staging; bad URLs are rejected before any network work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-28 15:59:36 -04:00

189 lines
6.9 KiB
Rust

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<String>,
// ── 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<String>,
/// Override display server autodetection.
#[arg(long, value_enum)]
pub display_server: Option<DisplayServerArg>,
/// 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<Quality>,
/// 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<u32>,
/// Encode bitrate in kbps. Overrides the quality preset's bitrate.
#[arg(long)]
pub bitrate: Option<u32>,
/// Capture framerate. Overrides the quality preset's framerate.
#[arg(long)]
pub framerate: Option<u32>,
/// 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<u32>,
// ── 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<String>,
/// 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<OutputFormat>,
/// 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,
/// 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,
}
#[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<String>,
pub display_server: Option<DisplayServerArg>,
/// 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<u32>,
/// Raw `--framerate` override; None = use the preset's framerate.
pub framerate: Option<u32>,
/// Raw `--max-height` override (px); None = use the preset's height.
pub max_height: Option<u32>,
pub no_hwencode: bool,
pub max_viewers: Option<u32>,
pub interactive: bool,
/// Relay override (resolved from `--relay` / `PIXELPASS_RELAY`); None = defaults.
pub relay: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ViewerOpts {
pub port: u16,
pub interactive: bool,
/// Relay override (resolved from `--relay` / `PIXELPASS_RELAY`); None = defaults.
pub relay: Option<String>,
}
impl Cli {
pub fn into_host_opts(self, interactive: bool) -> HostOpts {
HostOpts {
window: self.window,
app: self.app,
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()),
}
}
}