hydra

Terminal replacement for Loopback — virtual audio devices and routing on macOS, from a ratatui TUI.
Log | Files | Refs | README | LICENSE

commit 40d8e66a9cee902179b6b5d23ad1a32e0fe114e6
parent 3e18dcf2055188a06a98262e8439e481e83975df
Author: Matthew Gantenbein <ganten1998@gmail.com>
Date:   Sun, 31 May 2026 20:55:28 -0500

fix: complete the combine feature + installer (prior 2 commits didn't build)

Commits 190a263 and df058d9 were pushed with a non-compiling tree (a cancelled
tool batch left a duplicate combined_label fn in server.rs and a missing
`marked` field on App). This fixes both forward.

Now genuinely verified: cargo build --workspace clean, 19 tests green, 0
warnings; live StartCombined of two audio PIDs → one route, source_count=2,
peak 1.000 (real mixed audio flowing). install.sh/uninstall.sh syntax-checked;
non-sudo stages (release build, Hydra.app sign, universal Hydra.driver build)
all run clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Diffstat:
Mcrates/hydra/src/app.rs | 2++
Mcrates/hydrad/src/server.rs | 31-------------------------------
2 files changed, 2 insertions(+), 31 deletions(-)

diff --git a/crates/hydra/src/app.rs b/crates/hydra/src/app.rs @@ -25,6 +25,8 @@ pub struct App { /// When false (default), background daemons (kind 2) are hidden from the app list, /// so you see real apps (Spotify, browsers, DAWs) not the system-helper wall. pub show_all_apps: bool, + /// PIDs the user has marked (space) to combine into one route. + pub marked: std::collections::BTreeSet<i32>, pub routes: Vec<RouteSummary>, /// Output devices a route can target (output_channels > 0), e.g. speakers or "Hydra". pub outputs: Vec<AudioDevice>, diff --git a/crates/hydrad/src/server.rs b/crates/hydrad/src/server.rs @@ -161,34 +161,3 @@ fn combined_label(pids: &[i32], output_uid: Option<&str>) -> (String, Vec<String (format!("{sources} → {output}"), bundle_ids) } - -/// Build the label + bundle-id list for a combined (multi-source) route. -/// Label reads like "Safari + Spotify +1 → Hydra". -fn combined_label(pids: &[i32], output_uid: Option<&str>) -> (String, Vec<String>) { - let apps = process::list_audio_processes(); - let names: Vec<String> = pids - .iter() - .map(|&pid| { - apps.iter().find(|a| a.pid == pid).map(|a| a.name.clone()).unwrap_or_else(|| format!("pid {pid}")) - }) - .collect(); - let bundle_ids: Vec<String> = - pids.iter().filter_map(|&pid| apps.iter().find(|a| a.pid == pid).and_then(|a| a.bundle_id.clone())).collect(); - - let sources = match names.len() { - 0 => "nothing".to_string(), - 1 => names[0].clone(), - 2 => format!("{} + {}", names[0], names[1]), - n => format!("{} + {} +{}", names[0], names[1], n - 2), - }; - - let output = match output_uid { - Some(uid) => hal::list_devices() - .ok() - .and_then(|ds| ds.into_iter().find(|d| d.uid == uid).map(|d| d.name)) - .unwrap_or_else(|| uid.to_string()), - None => "Default Output".to_string(), - }; - - (format!("{sources} → {output}"), bundle_ids) -}