Improve error warnings and command behavior for vmic
- Updated `vmic route` to use "none" instead of "off" for source removal, standardizing terminology. - Added warnings for handling stale `pw-loopback` processes during `vmic edit` operations. - Enhanced logging for `vmic route` to provide detailed feedback on moved streams. - Ensured stable sorting of ports in PipeWire graph for consistent behavior.
This commit is contained in:
@@ -48,7 +48,7 @@ LIBCLANG_PATH = "/home/<you>/.local/share/vmic-build/libclang-venv/lib/python3.*
|
|||||||
vmic create <name> [-l] # create a vmic, optionally with self-monitor
|
vmic create <name> [-l] # create a vmic, optionally with self-monitor
|
||||||
vmic route <name> -i <app[:media]> # move an app's playback into the vmic
|
vmic route <name> -i <app[:media]> # move an app's playback into the vmic
|
||||||
vmic route <name> -o <app[:media]> # move an app's recording off the vmic
|
vmic route <name> -o <app[:media]> # move an app's recording off the vmic
|
||||||
vmic route <name> -s <source|off> # mix a hardware mic in, or remove it
|
vmic route <name> -s <source|none> # mix a hardware mic in, or remove it
|
||||||
vmic edit <name> -l <true|false> # toggle the self-monitor loopback
|
vmic edit <name> -l <true|false> # toggle the self-monitor loopback
|
||||||
vmic edit <name> -v <0.8|80> # self-monitor loopback volume
|
vmic edit <name> -v <0.8|80> # self-monitor loopback volume
|
||||||
vmic edit <name> -sv <0.8|80> # mixed-in source volume
|
vmic edit <name> -sv <0.8|80> # mixed-in source volume
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ pub struct RouteArgs {
|
|||||||
#[arg(short, long = "output", value_name = "APP[:MEDIA]")]
|
#[arg(short, long = "output", value_name = "APP[:MEDIA]")]
|
||||||
pub outputs: Vec<String>,
|
pub outputs: Vec<String>,
|
||||||
|
|
||||||
/// Mix a hardware source into the vmic, or "off" to remove it.
|
/// Mix a hardware source into the vmic, or "none" to remove it.
|
||||||
#[arg(short, long = "source", value_name = "SOURCE|off")]
|
#[arg(short, long = "source", value_name = "SOURCE|none")]
|
||||||
pub source: Option<String>,
|
pub source: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub fn run(args: EditArgs) -> Result<()> {
|
|||||||
let name = state::normalize(&args.name);
|
let name = state::normalize(&args.name);
|
||||||
let mut state = VmicState::load(&conn, &name)?;
|
let mut state = VmicState::load(&conn, &name)?;
|
||||||
let graph = PwGraph::connect()?;
|
let graph = PwGraph::connect()?;
|
||||||
|
warn_if_stale(&graph, &state);
|
||||||
|
|
||||||
if let Some(enable) = args.loopback {
|
if let Some(enable) = args.loopback {
|
||||||
edit_loopback(&graph, &mut state, enable)?;
|
edit_loopback(&graph, &mut state, enable)?;
|
||||||
@@ -30,6 +31,23 @@ pub fn run(args: EditArgs) -> Result<()> {
|
|||||||
state.save(&conn)
|
state.save(&conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Warns if the tracked pw-loopback processes aren't visible in the live
|
||||||
|
/// graph, so changes made below may silently fail to apply.
|
||||||
|
fn warn_if_stale(graph: &PwGraph, state: &VmicState) {
|
||||||
|
let alive = |id: Option<u32>| id.is_some_and(|id| graph.nodes().iter().any(|n| n.id == id));
|
||||||
|
if !alive(state.sink_node_id) {
|
||||||
|
ui::warn(&format!(
|
||||||
|
"vmic '{}' appears stale (matching pw-loopback process not found); changes may not apply!",
|
||||||
|
state.name
|
||||||
|
));
|
||||||
|
} else if !alive(state.mix_node_id) {
|
||||||
|
ui::warn(&format!(
|
||||||
|
"vmic '{}' mix loopback process not found; -sv changes may not apply!",
|
||||||
|
state.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn edit_loopback(graph: &PwGraph, state: &mut VmicState, enable: bool) -> Result<()> {
|
fn edit_loopback(graph: &PwGraph, state: &mut VmicState, enable: bool) -> Result<()> {
|
||||||
match (enable, state.loopback_id) {
|
match (enable, state.loopback_id) {
|
||||||
(true, Some(id)) => {
|
(true, Some(id)) => {
|
||||||
|
|||||||
@@ -44,12 +44,28 @@ fn move_stream(kind: &str, move_cmd: &str, target: &str, filter: &str) -> Result
|
|||||||
ui::warn(&format!("'{filter}': matched 0 streams, nothing moved."));
|
ui::warn(&format!("'{filter}': matched 0 streams, nothing moved."));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut moved = 0;
|
||||||
for m in &matches {
|
for m in &matches {
|
||||||
std::process::Command::new("pactl")
|
let status = std::process::Command::new("pactl")
|
||||||
.args([move_cmd, &m.id, target])
|
.args([move_cmd, &m.id, target])
|
||||||
.status()?;
|
.status()?;
|
||||||
|
if status.success() {
|
||||||
|
moved += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let total = matches.len();
|
||||||
|
if moved == total {
|
||||||
|
ui::info(&format!("'{filter}': matched {total} stream(s) -> moved to '{target}'."));
|
||||||
|
} else if moved == 0 {
|
||||||
|
ui::warn(&format!("'{filter}': matched {total} stream(s), but none could be moved to '{target}'."));
|
||||||
|
} else {
|
||||||
|
ui::warn(&format!(
|
||||||
|
"'{filter}': matched {total} stream(s), moved {moved} to '{target}' ({} failed).",
|
||||||
|
total - moved
|
||||||
|
));
|
||||||
}
|
}
|
||||||
ui::info(&format!("'{filter}': matched {} stream(s) -> moved to '{target}'.", matches.len()));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,13 @@ impl PwGraph {
|
|||||||
let Some(node) = s.nodes.values().find(|n| n.name == node_name) else {
|
let Some(node) = s.nodes.values().find(|n| n.name == node_name) else {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
};
|
};
|
||||||
s.ports.values().filter(|p| p.node_id == node.id).cloned().collect()
|
// Ports come out of a HashMap, whose iteration order is randomized
|
||||||
|
// per process - sort by id (assigned monotonically by PipeWire) so
|
||||||
|
// callers that fall back to "first"/"second" port (see
|
||||||
|
// `loopback::resolve_link_pairs`) get a stable, reproducible pick.
|
||||||
|
let mut ports: Vec<Port> = s.ports.values().filter(|p| p.node_id == node.id).cloned().collect();
|
||||||
|
ports.sort_by_key(|p| p.id);
|
||||||
|
ports
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn links(&self) -> Vec<Link> {
|
pub fn links(&self) -> Vec<Link> {
|
||||||
|
|||||||
Reference in New Issue
Block a user