From 96c079d29e26f85207eb31252013b4b571a7c774 Mon Sep 17 00:00:00 2001 From: Denis Drakhnia Date: Wed, 11 Oct 2023 11:17:30 +0300 Subject: [PATCH] ui: Display node media name in graph view --- src/graph_manager.rs | 18 +++++++++ src/main.rs | 5 +++ src/pipewire_connection/mod.rs | 73 +++++++++++++++++++++++++++++----- src/style.css | 2 +- src/ui/graph/node.rs | 25 ++++++++---- src/ui/graph/node.ui | 34 +++++++++++++--- 6 files changed, 133 insertions(+), 24 deletions(-) diff --git a/src/graph_manager.rs b/src/graph_manager.rs index e438552..34b2868 100644 --- a/src/graph_manager.rs +++ b/src/graph_manager.rs @@ -59,6 +59,7 @@ mod imp { move |msg| { match msg { PipewireMessage::NodeAdded { id, name, node_type } => imp.add_node(id, name.as_str(), node_type), + PipewireMessage::NodeNameChanged { id, name, media_name } => imp.node_name_changed(id, &name, &media_name), PipewireMessage::PortAdded { id, node_id, name, direction } => imp.add_port(id, name.as_str(), node_id, direction), PipewireMessage::PortFormatChanged { id, media_type } => imp.port_media_type_changed(id, media_type), PipewireMessage::LinkAdded { @@ -95,6 +96,23 @@ mod imp { self.obj().graph().add_node(node, node_type); } + /// Update a node tooltip to the view. + fn node_name_changed(&self, id: u32, node_name: &str, media_name: &str) { + let items = self.items.borrow(); + + let Some(node) = items.get(&id) else { + log::warn!("Node (id: {id}) for changed name not found in graph manager"); + return; + }; + let Some(node) = node.dynamic_cast_ref::() else { + log::warn!("Graph Manager item under node (id: {id}) is not a node"); + return; + }; + + node.set_node_name(node_name); + node.set_media_name(media_name); + } + /// Remove the node with the specified id from the view. fn remove_node(&self, id: u32) { log::info!("Removing node from graph: id {}", id); diff --git a/src/main.rs b/src/main.rs index b147c96..610c175 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,6 +39,11 @@ pub enum PipewireMessage { name: String, node_type: Option, }, + NodeNameChanged { + id: u32, + name: String, + media_name: String, + }, PortAdded { id: u32, node_id: u32, diff --git a/src/pipewire_connection/mod.rs b/src/pipewire_connection/mod.rs index a0cf429..768f737 100644 --- a/src/pipewire_connection/mod.rs +++ b/src/pipewire_connection/mod.rs @@ -26,7 +26,9 @@ use std::{ use adw::glib::{self, clone}; use log::{debug, error, info, warn}; use pipewire::{ + keys, link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState}, + node::{Node, NodeInfo, NodeListener}, port::{Port, PortChangeMask, PortInfo, PortListener}, prelude::*, properties, @@ -43,6 +45,10 @@ use crate::{GtkMessage, MediaType, NodeType, PipewireMessage}; use state::{Item, State}; enum ProxyItem { + Node { + _proxy: Node, + _listener: NodeListener, + }, Port { proxy: Port, _listener: PortListener, @@ -149,7 +155,7 @@ pub(super) fn thread_main( .add_listener_local() .global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state => move |global| match global.type_ { - ObjectType::Node => handle_node(global, >k_sender, &state), + ObjectType::Node => handle_node(global, >k_sender, ®istry, &proxies, &state), ObjectType::Port => handle_port(global, >k_sender, ®istry, &proxies, &state), ObjectType::Link => handle_link(global, >k_sender, ®istry, &proxies, &state), _ => { @@ -180,10 +186,21 @@ pub(super) fn thread_main( } } +/// Get the nicest possible name for the node, using a fallback chain of possible name attributes +fn get_node_name(props: &ForeignDict) -> &str { + props + .get(&keys::NODE_DESCRIPTION) + .or_else(|| props.get(&keys::NODE_NICK)) + .or_else(|| props.get(&keys::NODE_NAME)) + .unwrap_or_default() +} + /// Handle a new node being added fn handle_node( node: &GlobalObject, sender: &glib::Sender, + registry: &Rc, + proxies: &Rc>>, state: &Rc>, ) { let props = node @@ -191,15 +208,7 @@ fn handle_node( .as_ref() .expect("Node object is missing properties"); - // Get the nicest possible name for the node, using a fallback chain of possible name attributes. - let name = String::from( - props - .get("node.description") - .or_else(|| props.get("node.nick")) - .or_else(|| props.get("node.name")) - .unwrap_or_default(), - ); - + let name = get_node_name(props).to_string(); let media_class = |class: &str| { if class.contains("Sink") || class.contains("Input") { Some(NodeType::Input) @@ -230,6 +239,50 @@ fn handle_node( node_type, }) .expect("Failed to send message"); + + let proxy: Node = registry.bind(node).expect("Failed to bind to node proxy"); + let listener = proxy + .add_listener_local() + .info(clone!(@strong sender, @strong proxies => move |info| { + handle_node_info(info, &sender, &proxies); + })) + .register(); + + proxies.borrow_mut().insert( + node.id, + ProxyItem::Node { + _proxy: proxy, + _listener: listener, + }, + ); +} + +fn handle_node_info( + info: &NodeInfo, + sender: &glib::Sender, + proxies: &Rc>>, +) { + debug!("Received node info: {:?}", info); + + let id = info.id(); + let proxies = proxies.borrow(); + let Some(ProxyItem::Node { .. }) = proxies.get(&id) else { + error!("Received info on unknown node with id {id}"); + return; + }; + + let props = info.props().expect("NodeInfo object is missing properties"); + if let Some(media_name) = props.get(&keys::MEDIA_NAME) { + let name = get_node_name(props).to_string(); + + sender + .send(PipewireMessage::NodeNameChanged { + id, + name, + media_name: media_name.to_string(), + }) + .expect("Failed to send message"); + } } /// Handle a new port being added diff --git a/src/style.css b/src/style.css index f8fa650..397fedb 100644 --- a/src/style.css +++ b/src/style.css @@ -41,7 +41,7 @@ node { background-color: @headerbar_bg_color; } -node label.heading { +node .node-title { padding: 4px 7px; } diff --git a/src/ui/graph/node.rs b/src/ui/graph/node.rs index bb41ba8..54d71c6 100644 --- a/src/ui/graph/node.rs +++ b/src/ui/graph/node.rs @@ -34,15 +34,26 @@ mod imp { #[property(get, set, construct_only)] pub(super) pipewire_id: Cell, #[property( - name = "name", type = String, - get = |this: &Self| this.label.text().to_string(), + name = "node-name", type = String, + get = |this: &Self| this.node_name.text().to_string(), set = |this: &Self, val| { - this.label.set_text(val); - this.label.set_tooltip_text(Some(val)); + this.node_name.set_text(val); + this.node_name.set_tooltip_text(Some(val)); } )] #[template_child] - pub(super) label: TemplateChild, + pub(super) node_name: TemplateChild, + #[property( + name = "media-name", type = String, + get = |this: &Self| this.media_name.text().to_string(), + set = |this: &Self, val| { + this.media_name.set_text(val); + this.media_name.set_tooltip_text(Some(val)); + this.media_name.set_visible(!val.is_empty()); + } + )] + #[template_child] + pub(super) media_name: TemplateChild, #[template_child] pub(super) separator: TemplateChild, #[template_child] @@ -75,7 +86,7 @@ mod imp { self.parent_constructed(); // Display a grab cursor when the mouse is over the label so the user knows the node can be dragged. - self.label + self.node_name .set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref()); } @@ -141,7 +152,7 @@ glib::wrapper! { impl Node { pub fn new(name: &str, pipewire_id: u32) -> Self { glib::Object::builder() - .property("name", name) + .property("node-name", name) .property("pipewire-id", pipewire_id) .build() } diff --git a/src/ui/graph/node.ui b/src/ui/graph/node.ui index 8c53ada..9a80c56 100644 --- a/src/ui/graph/node.ui +++ b/src/ui/graph/node.ui @@ -9,14 +9,36 @@ vertical - + - true - PANGO_ELLIPSIZE_END - 2 - 20 + vertical + 1 + + + + true + PANGO_ELLIPSIZE_END + 2 + 20 + + + + + + false + true + PANGO_ELLIPSIZE_END + 2 + 20 + +