diff --git a/src/common/config.rs b/src/common/config.rs index 3edebc6..57a2c31 100644 --- a/src/common/config.rs +++ b/src/common/config.rs @@ -84,7 +84,6 @@ pub enum BandwidthStatus { Failed, } - fn default_status() -> BandwidthStatus { BandwidthStatus::Unmeasured } @@ -116,11 +115,9 @@ pub fn save(cfg: &Config) -> Result<()> { let parent = path .parent() .context("config path has no parent directory")?; - fs::create_dir_all(parent) - .with_context(|| format!("failed to create {}", parent.display()))?; + fs::create_dir_all(parent).with_context(|| format!("failed to create {}", parent.display()))?; - let serialized = - toml::to_string_pretty(cfg).context("failed to serialize config to TOML")?; + let serialized = toml::to_string_pretty(cfg).context("failed to serialize config to TOML")?; let tmp = parent.join(format!(".config.toml.tmp.{}", std::process::id())); { diff --git a/src/common/deps.rs b/src/common/deps.rs index 10202e0..b82ddb4 100644 --- a/src/common/deps.rs +++ b/src/common/deps.rs @@ -85,7 +85,9 @@ fn install_hint_for_bin(bin: &str) -> String { let distro = detect_distro(); let pkg = match bin { "gst-launch-1.0" | "gst-inspect-1.0" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gstreamer gst-plugins-base", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gstreamer gst-plugins-base" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-tools", Some("fedora" | "nobara") => "gstreamer1 gstreamer1-plugins-base-tools", _ => "gstreamer + tools", @@ -98,7 +100,9 @@ fn install_hint_for_bin(bin: &str) -> String { _ => "pulseaudio-utils (provides `pactl`)", }, "xwininfo" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "xorg-xwininfo", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "xorg-xwininfo" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "x11-utils", Some("fedora" | "nobara") => "xorg-x11-utils", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "xwininfo", @@ -113,56 +117,74 @@ fn install_hint_for_gst_element(name: &str) -> String { let distro = detect_distro(); let pkg = match name { "pipewiresrc" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugin-pipewire", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugin-pipewire" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-pipewire", Some("fedora" | "nobara") => "pipewire-gstreamer", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "pipewire-gstreamer", _ => "the GStreamer PipeWire plugin", }, "vah264enc" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugin-va", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugin-va" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-plugins-bad", Some("fedora" | "nobara") => "gstreamer1-plugins-bad-free", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-plugins-bad", - _ => "the GStreamer VA-API plugin (requires an H.264-capable GPU; almost all modern GPUs)", + _ => { + "the GStreamer VA-API plugin (requires an H.264-capable GPU; almost all modern GPUs)" + } }, "x264enc" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugins-ugly", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugins-ugly" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-plugins-ugly", Some("fedora" | "nobara") => "gstreamer1-plugins-ugly", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-plugins-ugly", _ => "the GStreamer x264 plugin (plugins-ugly)", }, "ximagesrc" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugins-good", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugins-good" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-plugins-good", Some("fedora" | "nobara") => "gstreamer1-plugins-good", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-plugins-good", _ => "the GStreamer X11 plugin (plugins-good)", }, "videoscale" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugins-base", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugins-base" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-plugins-base", Some("fedora" | "nobara") => "gstreamer1-plugins-base", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-plugins-base", _ => "the GStreamer plugins-base set", }, "h264parse" | "mpegtsmux" | "aacparse" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugins-bad", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugins-bad" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-plugins-bad", Some("fedora" | "nobara") => "gstreamer1-plugins-bad-free", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-plugins-bad", _ => "the GStreamer plugins-bad set", }, "pulsesrc" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-plugins-good", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-plugins-good" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-pulseaudio", Some("fedora" | "nobara") => "gstreamer1-plugins-good", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-plugins-good", _ => "the GStreamer PulseAudio plugin", }, "avenc_aac" => match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => "gst-libav", + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + "gst-libav" + } Some("debian" | "ubuntu" | "pop" | "linuxmint") => "gstreamer1.0-libav", Some("fedora" | "nobara") => "gstreamer1-libav", Some("opensuse" | "opensuse-tumbleweed" | "opensuse-leap") => "gstreamer-libav", @@ -175,10 +197,14 @@ fn install_hint_for_gst_element(name: &str) -> String { fn install_command(distro: &Option, pkg: &str) -> String { let cmd = match distro.as_deref() { - Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => format!("sudo pacman -S {pkg}"), + Some("arch" | "cachyos" | "manjaro" | "endeavouros" | "artix" | "garuda") => { + 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}"), + 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}") diff --git a/src/gui/child.rs b/src/gui/child.rs index 1f9f76f..b1233ef 100644 --- a/src/gui/child.rs +++ b/src/gui/child.rs @@ -261,12 +261,20 @@ mod tests { #[test] fn capture_state_round_trips() { assert!(matches!( - parse(Event::Capture { state: EmitState::Started }), - ChildEvent::Capture { state: CaptureState::Started } + parse(Event::Capture { + state: EmitState::Started + }), + ChildEvent::Capture { + state: CaptureState::Started + } )); assert!(matches!( - parse(Event::Capture { state: EmitState::Stopped }), - ChildEvent::Capture { state: CaptureState::Stopped } + parse(Event::Capture { + state: EmitState::Stopped + }), + ChildEvent::Capture { + state: CaptureState::Stopped + } )); } diff --git a/src/host/audio.rs b/src/host/audio.rs index 1b2f096..d1ba789 100644 --- a/src/host/audio.rs +++ b/src/host/audio.rs @@ -52,9 +52,8 @@ impl Routing { let pid = std::process::id(); let sink_name = format!("pixelpass_capture_{pid}"); - let sink_module = - load_module(&["module-null-sink", &format!("sink_name={sink_name}")]) - .context("failed to load module-null-sink")?; + let sink_module = load_module(&["module-null-sink", &format!("sink_name={sink_name}")]) + .context("failed to load module-null-sink")?; // 20ms loopback latency keeps the mirrored audio tight; pactl's // default of 200ms is enough to be perceptible. @@ -205,7 +204,9 @@ fn parse_sink_inputs(stdout: &[u8]) -> Result> { serde_json::from_slice(stdout).context("pactl returned unparseable JSON")?; let mut counts: BTreeMap = BTreeMap::new(); for entry in entries { - let Some(name) = entry.properties.application_name else { continue }; + let Some(name) = entry.properties.application_name else { + continue; + }; let trimmed = name.trim(); if trimmed.is_empty() { continue; @@ -359,16 +360,14 @@ fn run_router( ) -> Result<()> { use pipewire::{self as pw, types::ObjectType}; - let main_loop = pw::main_loop::MainLoopRc::new(None) - .context("pw main loop construction failed")?; - let context = pw::context::ContextRc::new(&main_loop, None) - .context("pw context construction failed")?; + let main_loop = + pw::main_loop::MainLoopRc::new(None).context("pw main loop construction failed")?; + let context = + pw::context::ContextRc::new(&main_loop, None).context("pw context construction failed")?; let core = context .connect_rc(None) .context("pw core connect failed (is the daemon running?)")?; - let registry = core - .get_registry_rc() - .context("pw get_registry failed")?; + let registry = core.get_registry_rc().context("pw get_registry failed")?; let state = Rc::new(RefCell::new(RouterState { sink_serial: None, @@ -409,20 +408,21 @@ fn run_router( let _reg_listener = registry .add_listener_local() .global(move |obj| { - let Some(reg) = registry_weak.upgrade() else { return }; + let Some(reg) = registry_weak.upgrade() else { + return; + }; match obj.type_ { ObjectType::Node => { - let Some(props) = obj.props.as_ref() else { return }; + let Some(props) = obj.props.as_ref() else { + return; + }; if props.get("node.name") == Some(sink_name_owned.as_str()) { if let Some(serial) = props .get("object.serial") .and_then(|s| s.parse::().ok()) { state_for_reg.borrow_mut().sink_serial = Some(serial); - tracing::info!( - serial, - "audio routing: pixelpass sink registered" - ); + tracing::info!(serial, "audio routing: pixelpass sink registered"); try_flush(&state_for_reg, &event_tx_for_reg); } return; @@ -430,7 +430,9 @@ fn run_router( if props.get("media.class") != Some("Stream/Output/Audio") { return; } - let Some(app) = props.get("application.name") else { return }; + let Some(app) = props.get("application.name") else { + return; + }; if !app.eq_ignore_ascii_case(&filter_lower) { return; } @@ -443,7 +445,9 @@ fn run_router( try_flush(&state_for_reg, &event_tx_for_reg); } ObjectType::Metadata => { - let Some(props) = obj.props.as_ref() else { return }; + let Some(props) = obj.props.as_ref() else { + return; + }; if props.get("metadata.name") != Some("default") { return; } diff --git a/src/host/mod.rs b/src/host/mod.rs index 0a26135..f18b458 100644 --- a/src/host/mod.rs +++ b/src/host/mod.rs @@ -256,7 +256,9 @@ fn spawn_kick_listener(sup_tx: mpsc::Sender) { let Some(id) = line.trim().strip_prefix("kick ") else { continue; }; - let msg = SupervisorMsg::KickViewer { id: id.trim().to_string() }; + let msg = SupervisorMsg::KickViewer { + id: id.trim().to_string(), + }; // blocking_send is valid here: this is a plain thread, not inside // the tokio runtime. An Err means the supervisor closed — stop. if sup_tx.blocking_send(msg).is_err() { @@ -315,7 +317,11 @@ async fn supervise( viewers.insert(id.clone(), cancel); let active = viewers.len() as u32; let _ = reply.send(Ok(port)); - output::emit(output::Event::ViewerJoined { id: &id, active, max: max_viewers }); + output::emit(output::Event::ViewerJoined { + id: &id, + active, + max: max_viewers, + }); tracing::info!(active, cap = max_viewers, "viewer joined"); } SupervisorMsg::RemoveViewer { id } => { @@ -325,7 +331,11 @@ async fn supervise( continue; } let active = viewers.len() as u32; - output::emit(output::Event::ViewerLeft { id: &id, active, max: max_viewers }); + output::emit(output::Event::ViewerLeft { + id: &id, + active, + max: max_viewers, + }); tracing::info!(active, cap = max_viewers, "viewer left"); if active == 0 && let Some(h) = handle.take() @@ -372,10 +382,25 @@ fn print_host_banner( eprintln!("┌─ PixelPass · host ─────────────────────────────────────────"); eprintln!("│ display server : {display:?}"); eprintln!("│ capture : {}", capture_summary(opts)); - eprintln!("│ quality : {} — {}", quality.label, quality.dimensions_summary()); + eprintln!( + "│ quality : {} — {}", + quality.label, + quality.dimensions_summary() + ); eprintln!("│ ({})", quality.note); - eprintln!("│ hw encode : {}", if opts.no_hwencode { "off (software x264)" } else { "on (VAAPI H.264)" }); - eprintln!("│ max viewers : {} ({})", resolution.value, resolution.source.label()); + eprintln!( + "│ hw encode : {}", + if opts.no_hwencode { + "off (software x264)" + } else { + "on (VAAPI H.264)" + } + ); + eprintln!( + "│ max viewers : {} ({})", + resolution.value, + resolution.source.label() + ); eprintln!("│"); if clipboard_ok { eprintln!("│ Your share code has been copied to your clipboard."); @@ -439,7 +464,9 @@ fn resolve_max_viewers(opts: &HostOpts, effective_bitrate: u32) -> MaxViewersRes let n = bandwidth::recommended_max_viewers(upstream, effective_bitrate); return MaxViewersResolution { value: n, - source: MaxViewersSource::BandwidthMeasurement { safe_mbps: upstream }, + source: MaxViewersSource::BandwidthMeasurement { + safe_mbps: upstream, + }, }; } MaxViewersResolution { diff --git a/src/host/pipeline.rs b/src/host/pipeline.rs index 723f0ef..0ec5638 100644 --- a/src/host/pipeline.rs +++ b/src/host/pipeline.rs @@ -308,7 +308,9 @@ async fn default_audio_monitor() -> Result { .arg("get-default-sink") .output() .await - .context("failed to run `pactl get-default-sink` (install pulseaudio-utils or pipewire-pulse)")?; + .context( + "failed to run `pactl get-default-sink` (install pulseaudio-utils or pipewire-pulse)", + )?; if !output.status.success() { bail!( "pactl get-default-sink failed: {}", diff --git a/src/host/quality.rs b/src/host/quality.rs index 294c348..ec5615c 100644 --- a/src/host/quality.rs +++ b/src/host/quality.rs @@ -27,10 +27,26 @@ impl Quality { /// values and resolves to one of the others at runtime (see [`resolve_auto`]). fn preset(self) -> Option { let p = match self { - Quality::Source => Preset { max_height: None, bitrate: 6000, framerate: 30 }, - Quality::High => Preset { max_height: Some(1080), bitrate: 4000, framerate: 30 }, - Quality::Medium => Preset { max_height: Some(720), bitrate: 2500, framerate: 30 }, - Quality::Low => Preset { max_height: Some(480), bitrate: 1000, framerate: 30 }, + Quality::Source => Preset { + max_height: None, + bitrate: 6000, + framerate: 30, + }, + Quality::High => Preset { + max_height: Some(1080), + bitrate: 4000, + framerate: 30, + }, + Quality::Medium => Preset { + max_height: Some(720), + bitrate: 2500, + framerate: 30, + }, + Quality::Low => Preset { + max_height: Some(480), + bitrate: 1000, + framerate: 30, + }, Quality::Auto => return None, }; Some(p) @@ -49,7 +65,12 @@ impl Quality { /// Fixed presets in descending quality order — Auto walks this to find the /// best one whose per-viewer bitrate fits the measured upstream budget. -const AUTO_LADDER: [Quality; 4] = [Quality::Source, Quality::High, Quality::Medium, Quality::Low]; +const AUTO_LADDER: [Quality; 4] = [ + Quality::Source, + Quality::High, + Quality::Medium, + Quality::Low, +]; /// Auto's fallback when there is no usable bandwidth measurement. const AUTO_FALLBACK: Quality = Quality::Medium; @@ -146,7 +167,9 @@ fn resolve_auto(safe_mbps: Option, sizing_viewers: u32) -> (Preset, String, ( preset, format!("Auto → {}", chosen.name()), - format!("auto: {safe_mbps:.1} Mbps safe ÷ {n} viewer(s) = {budget_mbps:.1} Mbps each"), + format!( + "auto: {safe_mbps:.1} Mbps safe ÷ {n} viewer(s) = {budget_mbps:.1} Mbps each" + ), ) } None => { @@ -154,7 +177,8 @@ fn resolve_auto(safe_mbps: Option, sizing_viewers: u32) -> (Preset, String, ( preset, format!("Auto → {}", AUTO_FALLBACK.name()), - "auto fallback — no bandwidth measurement (run `pixelpass --reconfigure`)".to_string(), + "auto fallback — no bandwidth measurement (run `pixelpass --reconfigure`)" + .to_string(), ) } } diff --git a/src/host/wayland.rs b/src/host/wayland.rs index 606160d..d41092b 100644 --- a/src/host/wayland.rs +++ b/src/host/wayland.rs @@ -26,7 +26,11 @@ pub async fn start(opts: &HostOpts, quality: &EffectiveQuality) -> Result Result Result { .items(["mpv", "VLC"]) .default(0) .interact()?; - Ok(if choice == 0 { Player::Mpv } else { Player::Vlc }) + Ok(if choice == 0 { + Player::Mpv + } else { + Player::Vlc + }) } diff --git a/src/main.rs b/src/main.rs index 2739ff4..fb0207b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,7 +73,11 @@ async fn main() -> Result<()> { } fn init_tracing(verbose: bool) { - let default = if verbose { "pixelpass=trace,iroh=info" } else { "pixelpass=info,iroh=warn" }; + let default = if verbose { + "pixelpass=trace,iroh=info" + } else { + "pixelpass=info,iroh=warn" + }; let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(default)); // Tracing MUST write to stderr. `tracing_subscriber::fmt()` defaults its // writer to stdout, but with `--output json` stdout carries the JSON event diff --git a/src/repair.rs b/src/repair.rs index f8a6899..0bc9d23 100644 --- a/src/repair.rs +++ b/src/repair.rs @@ -110,9 +110,7 @@ pub async fn run() -> Result<()> { } if live_skipped > 0 { - println!( - "[pixelpass] --repair: left {live_skipped} live pixelpass host(s) alone." - ); + println!("[pixelpass] --repair: left {live_skipped} live pixelpass host(s) alone."); } if failed > 0 { @@ -154,7 +152,9 @@ fn list_modules() -> Result> { for line in text.lines() { let mut parts = line.splitn(4, '\t'); let Some(id_str) = parts.next() else { continue }; - let Ok(id) = id_str.parse::() else { continue }; + let Ok(id) = id_str.parse::() else { + continue; + }; let Some(name) = parts.next() else { continue }; let args = parts.next().unwrap_or("").to_string(); modules.push(Module {