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,57 @@
|
||||
mod tunnel;
|
||||
|
||||
use anyhow::Result;
|
||||
use iroh::Endpoint;
|
||||
use iroh::endpoint::presets;
|
||||
use iroh_tickets::endpoint::EndpointTicket;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use crate::cli::ViewerOpts;
|
||||
use crate::common::{alpn::ALPN, signal};
|
||||
|
||||
pub async fn run(ticket: EndpointTicket, opts: ViewerOpts) -> Result<()> {
|
||||
let cancel = signal::install_ctrl_c();
|
||||
|
||||
let endpoint = Endpoint::builder(presets::N0)
|
||||
.alpns(vec![ALPN.to_vec()])
|
||||
.bind()
|
||||
.await?;
|
||||
|
||||
let addr = ticket.endpoint_addr().clone();
|
||||
tracing::info!(remote = %addr.id, "connecting to host");
|
||||
let conn = endpoint.connect(addr, ALPN).await?;
|
||||
let (quic_send, quic_recv) = conn.open_bi().await?;
|
||||
|
||||
let listener = TcpListener::bind(("127.0.0.1", opts.port)).await?;
|
||||
let port = listener.local_addr()?.port();
|
||||
print_viewer_banner(port);
|
||||
|
||||
let result = tokio::select! {
|
||||
accepted = listener.accept() => {
|
||||
let (tcp, peer) = accepted?;
|
||||
tracing::info!(%peer, "local viewer connected");
|
||||
crate::common::tunnel::bridge(quic_send, quic_recv, tcp).await
|
||||
}
|
||||
_ = cancel.cancelled() => {
|
||||
tracing::info!("ctrl-c received before local viewer connected");
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
endpoint.close().await;
|
||||
result
|
||||
}
|
||||
|
||||
fn print_viewer_banner(port: u16) {
|
||||
let url = format!("http://127.0.0.1:{port}");
|
||||
eprintln!();
|
||||
eprintln!("┌─ PixelPass · viewer ───────────────────────────────────────");
|
||||
eprintln!("│ Connected to host. Open the stream in your player:");
|
||||
eprintln!("│");
|
||||
eprintln!("│ mpv --profile=low-latency --untimed {url}");
|
||||
eprintln!("│ vlc --network-caching=200 --live-caching=200 {url}");
|
||||
eprintln!("│");
|
||||
eprintln!("│ Press Ctrl+C to disconnect.");
|
||||
eprintln!("└────────────────────────────────────────────────────────────");
|
||||
eprintln!();
|
||||
}
|
||||
Reference in New Issue
Block a user