First-run host launch now offers a one-time upstream measurement against speed.cloudflare.com/__up via ureq (~5 MB POST, ~5s). The result lives at ~/.config/pixelpass/config.toml under [bandwidth] and feeds the default --max-viewers calculation on subsequent runs. Sticky semantics for the dialog: - Unmeasured: first-run prompt (Run / Skip) - Measured / Skipped: silent — never re-prompts - Failed: ask again on next launch (Retry / give up → Skipped) `pixelpass --reconfigure` re-runs the test unconditionally for users whose connection has changed (new ISP, moved house, etc.). --max-viewers is now Option<u32>. When unset, host startup loads the saved measurement, runs recommended_max_viewers(safe_mbps, bitrate), and surfaces the source in the banner: "max viewers : N (auto: X.X Mbps measured upstream)" — or user-specified / default fallback. User verified end-to-end on 2026-05-21 16:54 EDT: first-run dialog, skip path, run path, --reconfigure refresh, and banner integration all work as expected. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
122 lines
3.7 KiB
Rust
122 lines
3.7 KiB
Rust
use clap::{Parser, ValueEnum};
|
|
|
|
#[derive(Parser, Debug)]
|
|
#[command(
|
|
name = "pixelpass",
|
|
version,
|
|
about = "P2P screen sharing over iroh + ffmpeg",
|
|
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 ──────────────────────────────────────────────────
|
|
/// 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>,
|
|
|
|
/// Mix in the default microphone source.
|
|
#[arg(long)]
|
|
pub mic: bool,
|
|
|
|
/// Override display server autodetection.
|
|
#[arg(long, value_enum)]
|
|
pub display_server: Option<DisplayServerArg>,
|
|
|
|
/// Encode bitrate in kbps.
|
|
#[arg(long, default_value_t = 6000)]
|
|
pub bitrate: u32,
|
|
|
|
/// Capture framerate.
|
|
#[arg(long, default_value_t = 30)]
|
|
pub framerate: u32,
|
|
|
|
/// Disable VAAPI HW encode; force software x264.
|
|
#[arg(long)]
|
|
pub no_hwencode: bool,
|
|
|
|
/// Use low-latency SRT transport instead of HTTP MPEG-TS (Phase 2/3).
|
|
#[arg(long)]
|
|
pub low_latency: 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 ────────────────────────────────────────────────────────
|
|
/// 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(Debug, Clone)]
|
|
pub struct HostOpts {
|
|
pub window: bool,
|
|
pub app: Option<String>,
|
|
pub mic: bool,
|
|
pub display_server: Option<DisplayServerArg>,
|
|
pub bitrate: u32,
|
|
pub framerate: u32,
|
|
pub no_hwencode: bool,
|
|
pub low_latency: bool,
|
|
pub max_viewers: Option<u32>,
|
|
pub interactive: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct ViewerOpts {
|
|
pub port: u16,
|
|
pub interactive: bool,
|
|
}
|
|
|
|
impl Cli {
|
|
pub fn into_host_opts(self, interactive: bool) -> HostOpts {
|
|
HostOpts {
|
|
window: self.window,
|
|
app: self.app,
|
|
mic: self.mic,
|
|
display_server: self.display_server,
|
|
bitrate: self.bitrate,
|
|
framerate: self.framerate,
|
|
no_hwencode: self.no_hwencode,
|
|
low_latency: self.low_latency,
|
|
max_viewers: self.max_viewers,
|
|
interactive,
|
|
}
|
|
}
|
|
|
|
pub fn into_viewer_opts(self, interactive: bool) -> ViewerOpts {
|
|
ViewerOpts { port: self.port, interactive }
|
|
}
|
|
}
|