diff --git a/src/application.rs b/src/application.rs index 5ec064e..8ec3e5e 100644 --- a/src/application.rs +++ b/src/application.rs @@ -109,8 +109,9 @@ impl Application { move |msg| { match msg { PipewireMessage::NodeAdded{ id, name } => app.add_node(id, name.as_str()), - PipewireMessage::PortAdded{ id, node_id, name, direction, media_type} => app.add_port(id, name.as_str(), node_id, direction, media_type), - PipewireMessage::LinkAdded{ id, node_from, port_from, node_to, port_to} => app.add_link(id, node_from, port_from, node_to, port_to), + PipewireMessage::PortAdded{ id, node_id, name, direction, media_type } => app.add_port(id, name.as_str(), node_id, direction, media_type), + PipewireMessage::LinkAdded{ id, node_from, port_from, node_to, port_to, active} => app.add_link(id, node_from, port_from, node_to, port_to, active), + PipewireMessage::LinkStateChanged { id, active } => app.link_state_changed(id, active), // TODO PipewireMessage::NodeRemoved { id } => app.remove_node(id), PipewireMessage::PortRemoved { id, node_id } => app.remove_port(id, node_id), PipewireMessage::LinkRemoved { id } => app.remove_link(id) @@ -124,7 +125,7 @@ impl Application { } /// Add a new node to the view. - pub fn add_node(&self, id: u32, name: &str) { + fn add_node(&self, id: u32, name: &str) { info!("Adding node to graph: id {}", id); imp::Application::from_instance(self) @@ -133,7 +134,7 @@ impl Application { } /// Add a new port to the view. - pub fn add_port( + fn add_port( &self, id: u32, name: &str, @@ -168,7 +169,15 @@ impl Application { } /// Add a new link to the view. - pub fn add_link(&self, id: u32, node_from: u32, port_from: u32, node_to: u32, port_to: u32) { + fn add_link( + &self, + id: u32, + node_from: u32, + port_from: u32, + node_to: u32, + port_to: u32, + active: bool, + ) { info!("Adding link to graph: id {}", id); // FIXME: Links should be colored depending on the data they carry (video, audio, midi) like ports are. @@ -182,9 +191,22 @@ impl Application { node_to, port_to, }, + active, ); } + fn link_state_changed(&self, id: u32, active: bool) { + info!( + "Link state changed: Link (id={}) is now {}", + id, + if active { "active" } else { "inactive" } + ); + + imp::Application::from_instance(self) + .graphview + .set_link_state(id, active); + } + // Toggle a link between the two specified ports on the remote pipewire server. fn toggle_link(&self, port_from: u32, port_to: u32) { let imp = imp::Application::from_instance(self); diff --git a/src/main.rs b/src/main.rs index eaa5992..239f39d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,6 +37,11 @@ enum PipewireMessage { port_from: u32, node_to: u32, port_to: u32, + active: bool, + }, + LinkStateChanged { + id: u32, + active: bool, }, NodeRemoved { id: u32, diff --git a/src/pipewire_connection.rs b/src/pipewire_connection.rs index 37ec270..7088309 100644 --- a/src/pipewire_connection.rs +++ b/src/pipewire_connection.rs @@ -5,7 +5,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use gtk::glib::{self, clone}; use log::{debug, info, warn}; use pipewire::{ - link::{Link, LinkListener}, + link::{Link, LinkChangeMask, LinkListener, LinkState}, prelude::*, properties, registry::{GlobalObject, Registry}, @@ -193,7 +193,13 @@ fn handle_link( let mut state = state.borrow_mut(); if let Some(Item::Link { .. }) = state.get(id) { // Info was an update - figure out if we should notify the gtk thread - // TODO + if info.change_mask().contains(LinkChangeMask::STATE) { + sender.send(PipewireMessage::LinkStateChanged { + id, + active: matches!(info.state(), LinkState::Active) + }).expect("Failed to send message"); + } + // TODO -- check other values that might have changed } else { // First time we get info. We can now notify the gtk thread of a new link. let node_from = info.output_node_id(); @@ -210,7 +216,8 @@ fn handle_link( node_from, port_from, node_to, - port_to + port_to, + active: matches!(info.state(), LinkState::Active) }).expect( "Failed to send message" ); diff --git a/src/view/graph_view.rs b/src/view/graph_view.rs index 749991a..228831a 100644 --- a/src/view/graph_view.rs +++ b/src/view/graph_view.rs @@ -20,7 +20,8 @@ mod imp { #[derive(Default)] pub struct GraphView { pub(super) nodes: RefCell>, - pub(super) links: RefCell>, + /// Stores the link and whether it is currently active. + pub(super) links: RefCell>, } #[glib::object_subclass] @@ -150,10 +151,17 @@ mod imp { .expect("Failed to get cairo context"); link_cr.set_line_width(2.0); link_cr.set_source_rgb(0.0, 0.0, 0.0); - for link in self.links.borrow().values() { + for (link, active) in self.links.borrow().values() { if let Some((from_x, from_y, to_x, to_y)) = self.get_link_coordinates(link) { link_cr.move_to(from_x, from_y); + // Use dashed line for inactive links, full line otherwise. + if *active { + link_cr.set_dash(&[], 0.0); + } else { + link_cr.set_dash(&[10.0, 5.0], 0.0); + } + // Place curve control offset by half the x distance between the two points. // This makes the curve scale well for varying distances between the two ports, // especially when the output port is farther right than the input port. @@ -276,12 +284,22 @@ impl GraphView { } } - pub fn add_link(&self, link_id: u32, link: crate::PipewireLink) { + pub fn add_link(&self, link_id: u32, link: crate::PipewireLink, active: bool) { let private = imp::GraphView::from_instance(self); - private.links.borrow_mut().insert(link_id, link); + private.links.borrow_mut().insert(link_id, (link, active)); self.queue_draw(); } + pub fn set_link_state(&self, link_id: u32, active: bool) { + let private = imp::GraphView::from_instance(self); + if let Some((_, state)) = private.links.borrow_mut().get_mut(&link_id) { + *state = active; + self.queue_draw(); + } else { + warn!("Link state changed on unknown link (id={})", link_id); + } + } + pub fn remove_link(&self, id: u32) { let private = imp::GraphView::from_instance(self); let mut links = private.links.borrow_mut();