Phase 1 foundation: CLI, iroh tunnel, lazy capture wiring
Scaffolding for PixelPass per ~/Documents/p2p-screenshare-plan.md §7 "Phase 1 — MVP". The full QUIC tunnel handshake works end-to-end (verified locally: host generates ticket, viewer dials through iroh's relay, open_bi succeeds, lazy capture is wired correctly). What's implemented: - Cargo project with deps locked: iroh 1.0.0-rc.0, iroh-tickets, tokio, clap, ashpd, pipewire-rs, x11rb, ashpd, anyhow, thiserror, tracing, nix, directories, uuid. - src/cli.rs: complete clap surface per plan §6 (--window, --app, --mic, --display-server, --bitrate, --framerate, --no-hwencode, --low-latency, --port, --verbose, --repair). - Mode dispatch in main.rs: EndpointTicket::from_str is the authoritative check; no regex / heuristics. - common/display.rs: WAYLAND_DISPLAY → DISPLAY → XDG_SESSION_TYPE precedence with --display-server override. - common/deps.rs: per-distro install hints (pacman/apt/dnf/zypper) parsing /etc/os-release. - common/alpn.rs: ALPN = b"pixelpass/0". - common/tunnel.rs: generic bidirectional bridge between an iroh bi-stream and any AsyncRead+AsyncWrite (typically a TCP socket). - common/signal.rs: ctrl-c -> CancellationToken; second ctrl-c hard exit. - host/mod.rs: build Endpoint, generate ticket, print banner, await first peer (lazy — no ffmpeg until peer connects), accept_bi, spawn capture, bridge to localhost ffmpeg HTTP listener. - host/capture.rs: stub returning Phase-2 error; the place X11 x11grab and Wayland ashpd+gst pipelines will land. - viewer/mod.rs: Endpoint, connect with ALPN, open_bi, TcpListener on 127.0.0.1, print copy-ready mpv/vlc commands, bridge. - repair.rs: stub for --repair PipeWire scan. iroh 1.0-rc renamed Node* -> Endpoint* and moved EndpointTicket into a sibling crate (iroh-tickets); no design impact. Plan still locked.
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
use clap::{Parser, ValueEnum};
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(
|
||||
name = "pixelpass",
|
||||
version,
|
||||
about = "P2P screen sharing over iroh + ffmpeg",
|
||||
long_about = "Run with no arguments to host (prints a ticket to share). \
|
||||
Pass a ticket to view."
|
||||
)]
|
||||
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,
|
||||
|
||||
// ── 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,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewerOpts {
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub fn into_host_opts(self) -> 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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_viewer_opts(self) -> ViewerOpts {
|
||||
ViewerOpts { port: self.port }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user