Files
pipewire-autoconnect/src/main.rs
2022-08-05 23:08:04 +01:00

383 lines
11 KiB
Rust

use std::{cell::RefCell, env, fs, io::BufRead, collections::HashMap};
use std::vec::Vec;
use std::{cell::RefMut, rc::Rc};
use libspa::ReadableDict;
use pipewire::{types::ObjectType, Context, MainLoop};
use pipewire as pw;
use regex::Regex;
#[macro_use]
extern crate lazy_static;
#[derive(Debug)]
struct Port {
id: u32,
name: String,
node: Rc<Node>,
}
#[derive(Debug)]
struct Node {
id: u32,
name: String,
}
#[derive(Debug)]
struct NodeDef {
name: String,
}
#[derive(Debug)]
struct PortDef {
node: Rc<NodeDef>,
name: String,
}
#[derive(Debug)]
struct LinkDef {
port_in: Rc<PortDef>,
port_out: Rc<PortDef>,
}
struct AppState {
ports: Vec<Rc<Port>>,
nodes: Vec<Rc<Node>>,
get_names: bool,
node_def: Vec<Rc<NodeDef>>,
link_def: Vec<Rc<LinkDef>>,
port_def: Vec<Rc<PortDef>>,
}
fn search<T, P>(v: &[Rc<T>], f: P) -> Option<Rc<T>>
where
T: Sized,
P: FnMut(&&Rc<T>) -> bool,
{
let n = v.iter().filter(f).cloned().collect::<Vec<Rc<T>>>();
if n.len() != 1 {
return None;
}
Some(n[0].clone())
}
impl AppState {
fn new(
node_def: Vec<Rc<NodeDef>>,
link_def: Vec<Rc<LinkDef>>,
port_def: Vec<Rc<PortDef>>,
get_names: bool,
) -> AppState {
AppState {
node_def,
link_def,
port_def,
get_names,
ports: Vec::new(),
nodes: Vec::new(),
}
}
fn try_add_node(&mut self, def: Node) -> bool {
if !self.node_def.iter().any(|a| a.name.eq(&def.name)) {
return false;
};
let mut nodes = self
.nodes
.iter()
.filter(|a| !a.name.eq(&def.name))
.cloned()
.collect::<Vec<Rc<Node>>>();
nodes.push(Rc::new(def));
self.nodes = nodes;
true
}
fn get_node(&self, id: u32) -> Option<Rc<Node>> {
search(&self.nodes, |a| a.id == id)
}
fn get_port_by_name(&self, name: String) -> Option<Rc<Port>> {
search(&self.ports, |a| a.name.eq(&name))
}
fn try_add_port(&mut self, id: u32, name: String, node_id: u32) -> bool {
let node = self.get_node(node_id);
if node.is_none() {
return false;
}
let node = node.unwrap();
if self
.port_def
.iter()
.filter(|a| a.name.eq(&name) && a.node.name.eq(&node.name))
.count()
!= 1
{
if self.get_names && node.id == node_id {
println!("Port from node {} -> {}: {}", &node.name, id, name);
}
return false;
}
let mut ports = self
.ports
.iter()
.filter(|a| !(a.name.eq(&name) && a.node.name.eq(&node.name)))
.cloned()
.collect::<Vec<Rc<Port>>>();
ports.push(Rc::new(Port { id, name, node }));
self.ports = ports;
true
}
fn create_links(&mut self, port_name: String, core: Rc<pw::Core>) {
#[derive(Debug)]
struct TempLink {
port_in: Option<Rc<Port>>,
port_out: Option<Rc<Port>>,
}
self.link_def
.iter()
.filter(|link| (link.port_in.name.eq(&port_name) || link.port_out.name.eq(&port_name)))
.map(|a| TempLink {
port_in: self.get_port_by_name(a.port_in.name.to_string()),
port_out: self.get_port_by_name(a.port_out.name.to_string()),
}).filter(|a| a.port_in.is_some() && a.port_out.is_some()).for_each(|a| {
let port_in = a.port_in.unwrap();
let port_out = a.port_out.unwrap();
println!("Try to created link: [{}]{} -> [{}]{}", port_out.node.name, port_out.name, port_in.node.name, port_in.name);
// Try to create the link
if core.create_object::<pw::link::Link, _>(
// The actual name for a link factory might be different for your system,
// you should probably obtain a factory from the registry.
"link-factory",
&pw::properties! {
"link.output.port" => port_out.id.to_string(),
"link.input.port" => port_in.id.to_string(),
"link.output.node" => port_out.node.id.to_string(),
"link.input.node" => port_in.node.id.to_string(),
"object.linger" => "1"
},
).is_err() {
println!("Failed to create link");
}
});
}
}
fn deal_with_node(
global_object: &pipewire::registry::GlobalObject<libspa::ForeignDict>,
mut state: RefMut<AppState>,
) {
if let Some(props) = &global_object.props {
if let (Some(class), Some(name)) = (props.get("media.class"), props.get("node.name")) {
if class.starts_with("Audio") {
if state.get_names {
println!(
"Got Audio device {}: {}({})",
global_object.id,
name,
props.get("node.nick").unwrap_or("<no nick>")
);
}
if state.try_add_node(Node {
id: global_object.id,
name: name.to_string(),
}) {
println!(
"Got {}: {}({})",
global_object.id,
name,
props.get("node.nick").unwrap_or("<no nick>")
);
}
}
}
} else {
println!("No props! Skiping id: {:?}", global_object.id);
}
}
fn deal_with_port(
port: &pipewire::registry::GlobalObject<libspa::ForeignDict>,
mut state: RefMut<AppState>,
core: Rc<pw::Core>,
) {
if let Some(props) = &port.props {
if let (Some(name), Some(node_id)) = (props.get("port.name"), props.get("node.id")) {
if let Ok(node_id) = node_id.parse::<u32>() {
if state.try_add_port(port.id, name.to_string(), node_id) {
println!(
"Got port {} for {}",
name,
state.get_node(node_id).unwrap().name
);
state.create_links(name.to_string(), core)
}
} else {
println!("Clould not parse {}'s node.id({})", name, node_id)
}
}
} else {
println!("No props! Skiping id: {}", port.id);
}
}
fn help() {
println!("Usage: \n");
println!("pw-autoconnect <filename> \n")
}
fn parse_file(path: std::path::PathBuf, get_names: bool) -> Result<AppState, Box<dyn std::error::Error>> {
let file = fs::File::open(path)?;
let reader = std::io::BufReader::new(file);
lazy_static! {
static ref RE: Regex = Regex::new("\\[(?P<node_out>.*)\\]\\((?P<port_out>.*)\\)\\s*->\\s*\\[(?P<node_in>.*)\\]\\((?P<port_in>.*)\\)").unwrap();
}
let mut node_def: HashMap<String, Rc<NodeDef>> = HashMap::new();
let mut port_def: HashMap<String, Rc<PortDef>> = HashMap::new();
let mut link_def: Vec<Rc<LinkDef>> = Vec::new();
for line in reader.lines() {
let line = line?;
if RE.is_match(&line) {
let caps = RE.captures(&line).unwrap();
println!("Found link: [{}]{} -> [{}]{}", &caps["node_out"], &caps["port_out"], &caps["node_in"], &caps["port_in"]);
let node_out = match node_def.get_mut(&caps["node_out"]) {
Some(node) => node.to_owned(),
None => {
let node = Rc::new(NodeDef {name: caps["node_out"].to_string()});
node_def.insert(caps["node_out"].to_string(), node.clone());
node
},
};
let node_in = match node_def.get_mut(&caps["node_in"]) {
Some(node) => node.to_owned(),
None => {
let node = Rc::new(NodeDef {name: caps["node_in"].to_string()});
node_def.insert(caps["node_in"].to_string(), node.clone());
node
},
};
let port_out = match port_def.get_mut(&caps["port_out"]) {
Some(port) => port.to_owned(),
None => {
let port = Rc::new(PortDef { node: node_out.clone(), name: caps["port_out"].to_string() } );
port_def.insert(caps["port_out"].to_string(), port.clone());
port
},
};
let port_in = match port_def.get_mut(&caps["port_in"]) {
Some(port) => port.to_owned(),
None => {
let port = Rc::new(PortDef { node: node_in.clone(), name: caps["port_in"].to_string() } );
port_def.insert(caps["port_in"].to_string(), port.clone());
port
},
};
let link = Rc::new(LinkDef { port_out: port_out.clone(), port_in: port_in.clone() });
link_def.push(link)
} else if !line.starts_with('#') {
println!("invalid line: {}", line);
}
}
let node_def = node_def.values().cloned().collect::<Vec<Rc<NodeDef>>>();
let port_def = port_def.values().cloned().collect::<Vec<Rc<PortDef>>>();
Ok(AppState::new(node_def, link_def, port_def, get_names))
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hello, world!");
let mut args = env::args();
// Skip the current directory
args.next();
let mut find_names = false;
let mut file_name = None;
for a in args {
if a.eq("-f") {
find_names = true;
continue;
}
if file_name.is_some() {
println!("File name already exists");
return Ok(());
} else {
file_name = Some(a);
}
}
if file_name.is_none() {
help();
return Ok(());
}
let file_name = file_name.unwrap();
let path = std::path::Path::new(&file_name);
if !path.exists() || !path.is_file() {
println!("File not found does not exists");
return Ok(());
}
// Create DeSized State
let state = RefCell::new(parse_file(path.to_path_buf(), find_names)?);
println!("\n\nGot state! Starting up\n\n");
let mainloop = MainLoop::new()?;
let context = Context::new(&mainloop)?;
let core = Rc::new(context.connect(None)?);
let registry = core.get_registry()?;
let _listener = registry
.add_listener_local()
.global(move |global| match global.type_ {
ObjectType::Port => deal_with_port(global, state.borrow_mut(), core.clone()),
ObjectType::Node => deal_with_node(global, state.borrow_mut()),
_ => (),
})
.register();
mainloop.run();
Ok(())
}