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:
@@ -0,0 +1,67 @@
|
||||
use anyhow::{Result, bail};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::common::display::DisplayServer;
|
||||
|
||||
pub fn check_host_binaries(display: DisplayServer) -> Result<()> {
|
||||
require("ffmpeg")?;
|
||||
if display == DisplayServer::Wayland {
|
||||
require("gst-launch-1.0")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn require(bin: &str) -> Result<PathBuf> {
|
||||
match which(bin) {
|
||||
Some(p) => Ok(p),
|
||||
None => {
|
||||
let hint = install_hint(bin);
|
||||
bail!("`{bin}` not found on PATH.\n{hint}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn which(bin: &str) -> Option<PathBuf> {
|
||||
let path = std::env::var_os("PATH")?;
|
||||
for dir in std::env::split_paths(&path) {
|
||||
let candidate = dir.join(bin);
|
||||
if candidate.is_file() {
|
||||
return Some(candidate);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn install_hint(bin: &str) -> String {
|
||||
let distro = detect_distro();
|
||||
let pkg = match bin {
|
||||
"ffmpeg" => "ffmpeg",
|
||||
"gst-launch-1.0" => match distro.as_deref() {
|
||||
Some("arch" | "cachyos" | "manjaro" | "endeavouros") => "gst-plugins-base gst-plugins-good",
|
||||
Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-tools gstreamer1.0-plugins-good",
|
||||
Some("fedora" | "nobara") => "gstreamer1-plugins-good",
|
||||
_ => "gstreamer (and tools / good-plugins)",
|
||||
},
|
||||
_ => bin,
|
||||
};
|
||||
|
||||
let cmd = match distro.as_deref() {
|
||||
Some("arch" | "cachyos" | "manjaro" | "endeavouros") => format!("sudo pacman -S {pkg}"),
|
||||
Some("debian" | "ubuntu" | "pop" | "linuxmint") => format!("sudo apt install {pkg}"),
|
||||
Some("fedora" | "nobara") => format!("sudo dnf install {pkg}"),
|
||||
Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => format!("sudo zypper install {pkg}"),
|
||||
_ => format!("install the `{pkg}` package via your distro's package manager"),
|
||||
};
|
||||
|
||||
format!("Install hint: {cmd}")
|
||||
}
|
||||
|
||||
fn detect_distro() -> Option<String> {
|
||||
let contents = std::fs::read_to_string("/etc/os-release").ok()?;
|
||||
for line in contents.lines() {
|
||||
if let Some(rest) = line.strip_prefix("ID=") {
|
||||
return Some(rest.trim_matches('"').to_lowercase());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
Reference in New Issue
Block a user