mirror of
https://gitlab.freedesktop.org/pipewire/helvum
synced 2026-03-16 03:56:12 +08:00
Color ports depending on the type of data (audio,video,midi) they carry
This commit is contained in:
29
src/main.rs
29
src/main.rs
@@ -6,6 +6,24 @@ use gtk::prelude::*;
|
|||||||
|
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
// FIXME: This should be in its own .css file.
|
||||||
|
static STYLE: &str = "
|
||||||
|
.audio {
|
||||||
|
background: rgb(50,100,240);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
background: rgb(200,200,0);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.midi {
|
||||||
|
background: rgb(200,0,50);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PipewireLink {
|
pub struct PipewireLink {
|
||||||
pub node_from: u32,
|
pub node_from: u32,
|
||||||
@@ -36,6 +54,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let app = gtk::Application::new(Some("org.freedesktop.pipewire.graphui"), Default::default())
|
let app = gtk::Application::new(Some("org.freedesktop.pipewire.graphui"), Default::default())
|
||||||
.expect("Application creation failed");
|
.expect("Application creation failed");
|
||||||
|
|
||||||
|
app.connect_startup(|_| {
|
||||||
|
// Load CSS from the STYLE variable.
|
||||||
|
let provider = gtk::CssProvider::new();
|
||||||
|
provider.load_from_data(STYLE.as_bytes());
|
||||||
|
gtk::StyleContext::add_provider_for_display(
|
||||||
|
>k::gdk::Display::get_default().expect("Error initializing gtk css provider."),
|
||||||
|
&provider,
|
||||||
|
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
app.connect_activate(move |app| {
|
||||||
let scrollwindow = gtk::ScrolledWindowBuilder::new()
|
let scrollwindow = gtk::ScrolledWindowBuilder::new()
|
||||||
.child(&*graphview.borrow())
|
.child(&*graphview.borrow())
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use crate::{view, PipewireLink};
|
use crate::{view, PipewireLink};
|
||||||
|
|
||||||
|
use gtk::WidgetExt;
|
||||||
use libspa::dict::ReadableDict;
|
use libspa::dict::ReadableDict;
|
||||||
|
use log::warn;
|
||||||
use pipewire::{
|
use pipewire::{
|
||||||
port::Direction,
|
port::Direction,
|
||||||
registry::{GlobalObject, ObjectType},
|
registry::{GlobalObject, ObjectType},
|
||||||
@@ -8,9 +10,20 @@ use pipewire::{
|
|||||||
|
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
|
enum MediaType {
|
||||||
|
Audio,
|
||||||
|
Video,
|
||||||
|
Midi,
|
||||||
|
}
|
||||||
|
|
||||||
enum Item {
|
enum Item {
|
||||||
Node(view::Node),
|
Node {
|
||||||
Port { node_id: u32 },
|
widget: view::Node,
|
||||||
|
media_type: Option<MediaType>,
|
||||||
|
},
|
||||||
|
Port {
|
||||||
|
node_id: u32,
|
||||||
|
},
|
||||||
Link,
|
Link,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +69,7 @@ impl PipewireState {
|
|||||||
let node_widget = crate::view::Node::new(&format!(
|
let node_widget = crate::view::Node::new(&format!(
|
||||||
"{}",
|
"{}",
|
||||||
node.props
|
node.props
|
||||||
|
.as_ref()
|
||||||
.map(|dict| String::from(
|
.map(|dict| String::from(
|
||||||
dict.get("node.nick")
|
dict.get("node.nick")
|
||||||
.or(dict.get("node.description"))
|
.or(dict.get("node.description"))
|
||||||
@@ -64,12 +78,38 @@ impl PipewireState {
|
|||||||
))
|
))
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// FIXME: This relies on the node being passed to us by the pipwire server before its port.
|
||||||
|
let media_type = node
|
||||||
|
.props
|
||||||
|
.map(|props| {
|
||||||
|
props.get("media.class").map(|class| {
|
||||||
|
if class.contains("Audio") {
|
||||||
|
Some(MediaType::Audio)
|
||||||
|
} else if class.contains("Video") {
|
||||||
|
Some(MediaType::Video)
|
||||||
|
} else if class.contains("Midi") {
|
||||||
|
Some(MediaType::Midi)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
self.graphview
|
self.graphview
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.add_node(node.id, node_widget.clone());
|
.add_node(node.id, node_widget.clone());
|
||||||
|
|
||||||
// Save the created widget so we can delete ports easier.
|
// Save the created widget so we can delete ports easier.
|
||||||
self.items.insert(node.id, Item::Node(node_widget));
|
self.items.insert(
|
||||||
|
node.id,
|
||||||
|
Item::Node {
|
||||||
|
widget: node_widget,
|
||||||
|
media_type,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_port(&mut self, port: GlobalObject) {
|
fn add_port(&mut self, port: GlobalObject) {
|
||||||
@@ -91,6 +131,18 @@ impl PipewireState {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Color the port accordingly to its media class.
|
||||||
|
if let Some(Item::Node { media_type, .. }) = self.items.get(&node_id) {
|
||||||
|
match media_type {
|
||||||
|
Some(MediaType::Audio) => new_port.widget.add_css_class("audio"),
|
||||||
|
Some(MediaType::Video) => new_port.widget.add_css_class("video"),
|
||||||
|
Some(MediaType::Midi) => new_port.widget.add_css_class("midi"),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Node not found for Port {}", port.id);
|
||||||
|
}
|
||||||
|
|
||||||
self.graphview
|
self.graphview
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.add_port_to_node(node_id, new_port.id, new_port);
|
.add_port_to_node(node_id, new_port.id, new_port);
|
||||||
@@ -100,6 +152,7 @@ impl PipewireState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn add_link(&mut self, link: GlobalObject) {
|
fn add_link(&mut self, link: GlobalObject) {
|
||||||
|
// FIXME: Links should be colored depending on the data they carry (video, audio, midi) like ports are.
|
||||||
self.items.insert(link.id, Item::Link);
|
self.items.insert(link.id, Item::Link);
|
||||||
|
|
||||||
// Update graph to contain the new link.
|
// Update graph to contain the new link.
|
||||||
@@ -139,7 +192,7 @@ impl PipewireState {
|
|||||||
pub fn global_remove(&mut self, id: u32) {
|
pub fn global_remove(&mut self, id: u32) {
|
||||||
if let Some(item) = self.items.get(&id) {
|
if let Some(item) = self.items.get(&id) {
|
||||||
match item {
|
match item {
|
||||||
Item::Node(_) => self.remove_node(id),
|
Item::Node { .. } => self.remove_node(id),
|
||||||
Item::Port { node_id } => self.remove_port(id, *node_id),
|
Item::Port { node_id } => self.remove_port(id, *node_id),
|
||||||
Item::Link => self.remove_link(id),
|
Item::Link => self.remove_link(id),
|
||||||
}
|
}
|
||||||
@@ -158,8 +211,8 @@ impl PipewireState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn remove_port(&self, id: u32, node_id: u32) {
|
fn remove_port(&self, id: u32, node_id: u32) {
|
||||||
if let Some(Item::Node(node)) = self.items.get(&node_id) {
|
if let Some(Item::Node { widget, .. }) = self.items.get(&node_id) {
|
||||||
node.remove_port(id);
|
widget.remove_port(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/// Graphical representation of a pipewire port.
|
/// Graphical representation of a pipewire port.
|
||||||
pub struct Port {
|
pub struct Port {
|
||||||
pub(super) widget: gtk::Button,
|
pub widget: gtk::Button,
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub direction: pipewire::port::Direction,
|
pub direction: pipewire::port::Direction,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user