mirror of
https://gitlab.freedesktop.org/pipewire/helvum
synced 2026-03-15 03:26:10 +08:00
Color links and ports according to their formats
Format params for links and ports are now being watched for in the pipewire connection code. The parsed media type is then set on the port widget / link object and they are colored accordingly. For ports, which were already colored before, this new method of determining the media type should be more reliable and accurate as this uses the real Format/EnumFormat params instead of parsing optional properties.
This commit is contained in:
@@ -55,10 +55,14 @@ mod imp {
|
||||
@weak self as imp => @default-return glib::ControlFlow::Continue,
|
||||
move |msg| {
|
||||
match msg {
|
||||
PipewireMessage::NodeAdded{ id, name, node_type } => imp.add_node(id, name.as_str(), node_type),
|
||||
PipewireMessage::PortAdded{ id, node_id, name, direction, media_type } => imp.add_port(id, name.as_str(), node_id, direction, media_type),
|
||||
PipewireMessage::LinkAdded{ id, port_from, port_to, active} => imp.add_link(id, port_from, port_to, active),
|
||||
PipewireMessage::NodeAdded { id, name, node_type } => imp.add_node(id, name.as_str(), node_type),
|
||||
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 {
|
||||
id, port_from, port_to, active, media_type
|
||||
} => imp.add_link(id, port_from, port_to, active, media_type),
|
||||
PipewireMessage::LinkStateChanged { id, active } => imp.link_state_changed(id, active),
|
||||
PipewireMessage::LinkFormatChanged { id, media_type } => imp.link_format_changed(id, media_type),
|
||||
PipewireMessage::NodeRemoved { id } => imp.remove_node(id),
|
||||
PipewireMessage::PortRemoved { id, node_id } => imp.remove_port(id, node_id),
|
||||
PipewireMessage::LinkRemoved { id } => imp.remove_link(id)
|
||||
@@ -96,14 +100,7 @@ mod imp {
|
||||
}
|
||||
|
||||
/// Add a new port to the view.
|
||||
fn add_port(
|
||||
&self,
|
||||
id: u32,
|
||||
name: &str,
|
||||
node_id: u32,
|
||||
direction: pipewire::spa::Direction,
|
||||
media_type: Option<MediaType>,
|
||||
) {
|
||||
fn add_port(&self, id: u32, name: &str, node_id: u32, direction: pipewire::spa::Direction) {
|
||||
log::info!("Adding port to graph: id {}", id);
|
||||
|
||||
let mut items = self.items.borrow_mut();
|
||||
@@ -117,7 +114,7 @@ mod imp {
|
||||
return;
|
||||
};
|
||||
|
||||
let port = graph::Port::new(id, name, direction, media_type);
|
||||
let port = graph::Port::new(id, name, direction);
|
||||
|
||||
// Create or delete a link if the widget emits the "port-toggled" signal.
|
||||
port.connect_local(
|
||||
@@ -139,6 +136,21 @@ mod imp {
|
||||
node.add_port(port);
|
||||
}
|
||||
|
||||
fn port_media_type_changed(&self, id: u32, media_type: MediaType) {
|
||||
let items = self.items.borrow();
|
||||
|
||||
let Some(port) = items.get(&id) else {
|
||||
log::warn!("Port (id: {id}) for changed media type not found in graph manager");
|
||||
return;
|
||||
};
|
||||
let Some(port) = port.dynamic_cast_ref::<graph::Port>() else {
|
||||
log::warn!("Graph Manager item under port id {id} is not a port");
|
||||
return;
|
||||
};
|
||||
|
||||
port.set_media_type(media_type.as_raw())
|
||||
}
|
||||
|
||||
/// Remove the port with the id `id` from the node with the id `node_id`
|
||||
/// from the view.
|
||||
fn remove_port(&self, id: u32, node_id: u32) {
|
||||
@@ -167,7 +179,14 @@ mod imp {
|
||||
}
|
||||
|
||||
/// Add a new link to the view.
|
||||
fn add_link(&self, id: u32, output_port_id: u32, input_port_id: u32, active: bool) {
|
||||
fn add_link(
|
||||
&self,
|
||||
id: u32,
|
||||
output_port_id: u32,
|
||||
input_port_id: u32,
|
||||
active: bool,
|
||||
media_type: MediaType,
|
||||
) {
|
||||
log::info!("Adding link to graph: id {}", id);
|
||||
|
||||
let mut items = self.items.borrow_mut();
|
||||
@@ -193,6 +212,7 @@ mod imp {
|
||||
link.set_output_port(Some(&output_port));
|
||||
link.set_input_port(Some(&input_port));
|
||||
link.set_active(active);
|
||||
link.set_media_type(media_type);
|
||||
|
||||
items.insert(id, link.clone().upcast());
|
||||
|
||||
@@ -223,6 +243,20 @@ mod imp {
|
||||
link.set_active(active);
|
||||
}
|
||||
|
||||
fn link_format_changed(&self, id: u32, media_type: pipewire::spa::format::MediaType) {
|
||||
let items = self.items.borrow();
|
||||
|
||||
let Some(link) = items.get(&id) else {
|
||||
log::warn!("Link (id: {id}) for changed media type not found in graph manager");
|
||||
return;
|
||||
};
|
||||
let Some(link) = link.dynamic_cast_ref::<graph::Link>() else {
|
||||
log::warn!("Graph Manager item under link id {id} is not a link");
|
||||
return;
|
||||
};
|
||||
link.set_media_type(media_type);
|
||||
}
|
||||
|
||||
// Toggle a link between the two specified ports on the remote pipewire server.
|
||||
fn toggle_link(&self, port_from: u32, port_to: u32) {
|
||||
let sender = self.pw_sender.get().expect("pw_sender shoud be set");
|
||||
|
||||
19
src/main.rs
19
src/main.rs
@@ -20,7 +20,7 @@ mod pipewire_connection;
|
||||
mod ui;
|
||||
|
||||
use gtk::prelude::*;
|
||||
use pipewire::spa::Direction;
|
||||
use pipewire::spa::{format::MediaType, Direction};
|
||||
|
||||
/// Messages sent by the GTK thread to notify the pipewire thread.
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -44,18 +44,26 @@ pub enum PipewireMessage {
|
||||
node_id: u32,
|
||||
name: String,
|
||||
direction: Direction,
|
||||
media_type: Option<MediaType>,
|
||||
},
|
||||
PortFormatChanged {
|
||||
id: u32,
|
||||
media_type: MediaType,
|
||||
},
|
||||
LinkAdded {
|
||||
id: u32,
|
||||
port_from: u32,
|
||||
port_to: u32,
|
||||
active: bool,
|
||||
media_type: MediaType,
|
||||
},
|
||||
LinkStateChanged {
|
||||
id: u32,
|
||||
active: bool,
|
||||
},
|
||||
LinkFormatChanged {
|
||||
id: u32,
|
||||
media_type: MediaType,
|
||||
},
|
||||
NodeRemoved {
|
||||
id: u32,
|
||||
},
|
||||
@@ -74,13 +82,6 @@ pub enum NodeType {
|
||||
Output,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum MediaType {
|
||||
Audio,
|
||||
Video,
|
||||
Midi,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PipewireLink {
|
||||
pub node_from: u32,
|
||||
|
||||
@@ -21,11 +21,15 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
use gtk::glib::{self, clone};
|
||||
use log::{debug, info, warn};
|
||||
use pipewire::{
|
||||
link::{Link, LinkChangeMask, LinkListener, LinkState},
|
||||
link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState},
|
||||
port::{Port, PortChangeMask, PortInfo, PortListener},
|
||||
prelude::*,
|
||||
properties,
|
||||
registry::{GlobalObject, Registry},
|
||||
spa::{Direction, ForeignDict},
|
||||
spa::{
|
||||
param::{ParamInfoFlags, ParamType},
|
||||
ForeignDict,
|
||||
},
|
||||
types::ObjectType,
|
||||
Context, Core, MainLoop,
|
||||
};
|
||||
@@ -34,6 +38,10 @@ use crate::{GtkMessage, MediaType, NodeType, PipewireMessage};
|
||||
use state::{Item, State};
|
||||
|
||||
enum ProxyItem {
|
||||
Port {
|
||||
proxy: Port,
|
||||
_listener: PortListener,
|
||||
},
|
||||
Link {
|
||||
_proxy: Link,
|
||||
_listener: LinkListener,
|
||||
@@ -67,7 +75,7 @@ pub(super) fn thread_main(
|
||||
.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::Port => handle_port(global, >k_sender, &state),
|
||||
ObjectType::Port => handle_port(global, >k_sender, ®istry, &proxies, &state),
|
||||
ObjectType::Link => handle_link(global, >k_sender, ®istry, &proxies, &state),
|
||||
_ => {
|
||||
// Other objects are not interesting to us
|
||||
@@ -115,19 +123,6 @@ fn handle_node(
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
// FIXME: Instead of checking these props, the "EnumFormat" parameter should be checked instead.
|
||||
let media_type = props.get("media.class").and_then(|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
|
||||
}
|
||||
});
|
||||
|
||||
let media_class = |class: &str| {
|
||||
if class.contains("Sink") || class.contains("Input") {
|
||||
Some(NodeType::Input)
|
||||
@@ -149,13 +144,7 @@ fn handle_node(
|
||||
})
|
||||
.or_else(|| props.get("media.class").and_then(media_class));
|
||||
|
||||
state.borrow_mut().insert(
|
||||
node.id,
|
||||
Item::Node {
|
||||
// widget: node_widget,
|
||||
media_type,
|
||||
},
|
||||
);
|
||||
state.borrow_mut().insert(node.id, Item::Node);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::NodeAdded {
|
||||
@@ -170,44 +159,106 @@ fn handle_node(
|
||||
fn handle_port(
|
||||
port: &GlobalObject<ForeignDict>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
registry: &Rc<Registry>,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
) {
|
||||
let props = port
|
||||
.props
|
||||
.as_ref()
|
||||
.expect("Port object is missing properties");
|
||||
let name = props.get("port.name").unwrap_or_default().to_string();
|
||||
let node_id: u32 = props
|
||||
.get("node.id")
|
||||
.expect("Port has no node.id property!")
|
||||
.parse()
|
||||
.expect("Could not parse node.id property");
|
||||
let direction = if matches!(props.get("port.direction"), Some("in")) {
|
||||
Direction::Input
|
||||
} else {
|
||||
Direction::Output
|
||||
let port_id = port.id;
|
||||
let proxy: Port = registry.bind(port).expect("Failed to bind to port proxy");
|
||||
let listener = proxy
|
||||
.add_listener_local()
|
||||
.info(
|
||||
clone!(@strong proxies, @strong state, @strong sender => move |info| {
|
||||
handle_port_info(info, &proxies, &state, &sender);
|
||||
}),
|
||||
)
|
||||
.param(clone!(@strong sender => move |_, param_id, _, _, param| {
|
||||
if param_id == ParamType::EnumFormat {
|
||||
handle_port_enum_format(port_id, param, &sender)
|
||||
}
|
||||
}))
|
||||
.register();
|
||||
|
||||
proxies.borrow_mut().insert(
|
||||
port.id,
|
||||
ProxyItem::Port {
|
||||
proxy,
|
||||
_listener: listener,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_port_info(
|
||||
info: &PortInfo,
|
||||
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
|
||||
state: &Rc<RefCell<State>>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
) {
|
||||
debug!("Received port info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
let proxies = proxies.borrow();
|
||||
let Some(ProxyItem::Port { proxy, .. }) = proxies.get(&id) else {
|
||||
log::error!("Received info on unknown port with id {id}");
|
||||
return;
|
||||
};
|
||||
|
||||
// Find out the nodes media type so that the port can be colored.
|
||||
let media_type = if let Some(Item::Node { media_type, .. }) = state.borrow().get(node_id) {
|
||||
media_type.to_owned()
|
||||
} else {
|
||||
warn!("Node not found for Port {}", port.id);
|
||||
None
|
||||
};
|
||||
let mut state = state.borrow_mut();
|
||||
|
||||
// Save node_id so we can delete this port easily.
|
||||
state.borrow_mut().insert(port.id, Item::Port { node_id });
|
||||
if let Some(Item::Port { .. }) = state.get(id) {
|
||||
// Info was an update, figure out if we should notify the GTK thread
|
||||
if info.change_mask().contains(PortChangeMask::PARAMS) {
|
||||
// TODO: React to param changes
|
||||
}
|
||||
} else {
|
||||
// First time we get info. We can now notify the gtk thread of a new link.
|
||||
let props = info.props().expect("Port object is missing properties");
|
||||
let name = props.get("port.name").unwrap_or_default().to_string();
|
||||
let node_id: u32 = props
|
||||
.get("node.id")
|
||||
.expect("Port has no node.id property!")
|
||||
.parse()
|
||||
.expect("Could not parse node.id property");
|
||||
|
||||
state.insert(id, Item::Port { node_id });
|
||||
|
||||
let params = info.params();
|
||||
let enum_format_info = params
|
||||
.iter()
|
||||
.find(|param| param.id() == ParamType::EnumFormat);
|
||||
if let Some(enum_format_info) = enum_format_info {
|
||||
if enum_format_info.flags().contains(ParamInfoFlags::READ) {
|
||||
proxy.enum_params(0, Some(ParamType::EnumFormat), 0, u32::MAX);
|
||||
}
|
||||
}
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortAdded {
|
||||
id,
|
||||
node_id,
|
||||
name,
|
||||
direction: info.direction(),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_port_enum_format(
|
||||
port_id: u32,
|
||||
param: Option<&pipewire::spa::pod::Pod>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
) {
|
||||
let media_type = param
|
||||
.and_then(|param| pipewire::spa::param::format_utils::parse_format(param).ok())
|
||||
.map(|(media_type, _media_subtype)| media_type)
|
||||
.unwrap_or(MediaType::Unknown);
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::PortAdded {
|
||||
id: port.id,
|
||||
node_id,
|
||||
name,
|
||||
direction,
|
||||
.send(PipewireMessage::PortFormatChanged {
|
||||
id: port_id,
|
||||
media_type,
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
.expect("Failed to send message")
|
||||
}
|
||||
|
||||
/// Handle a new link being added
|
||||
@@ -227,38 +278,7 @@ fn handle_link(
|
||||
let listener = proxy
|
||||
.add_listener_local()
|
||||
.info(clone!(@strong state, @strong sender => move |info| {
|
||||
debug!("Received link info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
|
||||
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
|
||||
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 port_from = info.output_port_id();
|
||||
let port_to = info.input_port_id();
|
||||
|
||||
state.insert(id, Item::Link {
|
||||
port_from, port_to
|
||||
});
|
||||
|
||||
sender.send(PipewireMessage::LinkAdded {
|
||||
id,
|
||||
port_from,
|
||||
port_to,
|
||||
active: matches!(info.state(), LinkState::Active)
|
||||
}).expect(
|
||||
"Failed to send message"
|
||||
);
|
||||
}
|
||||
handle_link_info(info, &state, &sender);
|
||||
}))
|
||||
.register();
|
||||
|
||||
@@ -271,6 +291,53 @@ fn handle_link(
|
||||
);
|
||||
}
|
||||
|
||||
fn handle_link_info(
|
||||
info: &LinkInfo,
|
||||
state: &Rc<RefCell<State>>,
|
||||
sender: &glib::Sender<PipewireMessage>,
|
||||
) {
|
||||
debug!("Received link info: {:?}", info);
|
||||
|
||||
let id = info.id();
|
||||
|
||||
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
|
||||
if info.change_mask().contains(LinkChangeMask::STATE) {
|
||||
sender
|
||||
.send(PipewireMessage::LinkStateChanged {
|
||||
id,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
if info.change_mask().contains(LinkChangeMask::FORMAT) {
|
||||
sender
|
||||
.send(PipewireMessage::LinkFormatChanged {
|
||||
id,
|
||||
media_type: get_link_media_type(info),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
} else {
|
||||
// First time we get info. We can now notify the gtk thread of a new link.
|
||||
let port_from = info.output_port_id();
|
||||
let port_to = info.input_port_id();
|
||||
|
||||
state.insert(id, Item::Link { port_from, port_to });
|
||||
|
||||
sender
|
||||
.send(PipewireMessage::LinkAdded {
|
||||
id,
|
||||
port_from,
|
||||
port_to,
|
||||
active: matches!(info.state(), LinkState::Active),
|
||||
media_type: get_link_media_type(info),
|
||||
})
|
||||
.expect("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle a link between the two specified ports.
|
||||
fn toggle_link(
|
||||
port_from: u32,
|
||||
@@ -312,3 +379,13 @@ fn toggle_link(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_link_media_type(link_info: &LinkInfo) -> MediaType {
|
||||
let media_type = link_info
|
||||
.format()
|
||||
.and_then(|format| pipewire::spa::param::format_utils::parse_format(format).ok())
|
||||
.map(|(media_type, _media_subtype)| media_type)
|
||||
.unwrap_or(MediaType::Unknown);
|
||||
|
||||
media_type
|
||||
}
|
||||
|
||||
@@ -16,15 +16,10 @@
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::MediaType;
|
||||
|
||||
/// Any pipewire item we need to keep track of.
|
||||
/// These will be saved in the `State` struct associated with their id.
|
||||
pub(super) enum Item {
|
||||
Node {
|
||||
// Keep track of the nodes media type to color ports on it.
|
||||
media_type: Option<MediaType>,
|
||||
},
|
||||
Node,
|
||||
Port {
|
||||
// Save the id of the node this is on so we can remove the port from it
|
||||
// when it is deleted.
|
||||
|
||||
@@ -15,23 +15,23 @@
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
*/
|
||||
|
||||
@define-color audio rgb(50,100,240);
|
||||
@define-color video rgb(200,200,0);
|
||||
@define-color midi rgb(200,0,50);
|
||||
@define-color graphview-link #808080;
|
||||
@define-color media-type-audio rgb( 50, 100, 240);
|
||||
@define-color media-type-video rgb(200, 200, 0);
|
||||
@define-color media-type-midi rgb(200, 0, 50);
|
||||
@define-color media-type-unknown rgb(128, 128, 128);
|
||||
|
||||
.audio {
|
||||
background: @audio;
|
||||
background: @media-type-audio;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.video {
|
||||
background: @video;
|
||||
background: @media-type-video;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.midi {
|
||||
background: @midi;
|
||||
background: @media-type-midi;
|
||||
color: black;
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,27 @@ mod imp {
|
||||
};
|
||||
use log::warn;
|
||||
use once_cell::sync::Lazy;
|
||||
use pipewire::spa::format::MediaType;
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
pub struct Colors {
|
||||
audio: gdk::RGBA,
|
||||
video: gdk::RGBA,
|
||||
midi: gdk::RGBA,
|
||||
unknown: gdk::RGBA,
|
||||
}
|
||||
|
||||
impl Colors {
|
||||
pub fn color_for_media_type(&self, media_type: MediaType) -> &gdk::RGBA {
|
||||
match media_type {
|
||||
MediaType::Audio => &self.audio,
|
||||
MediaType::Video => &self.video,
|
||||
MediaType::Stream | MediaType::Application => &self.midi,
|
||||
_ => &self.unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DragState {
|
||||
node: glib::WeakRef<Node>,
|
||||
/// This stores the offset of the pointer to the origin of the node,
|
||||
@@ -510,6 +529,7 @@ mod imp {
|
||||
output_anchor: &Point,
|
||||
input_anchor: &Point,
|
||||
active: bool,
|
||||
color: &gdk::RGBA,
|
||||
) {
|
||||
let output_x: f64 = output_anchor.x().into();
|
||||
let output_y: f64 = output_anchor.y().into();
|
||||
@@ -523,6 +543,13 @@ mod imp {
|
||||
link_cr.set_dash(&[10.0, 5.0], 0.0);
|
||||
}
|
||||
|
||||
link_cr.set_source_rgba(
|
||||
color.red().into(),
|
||||
color.green().into(),
|
||||
color.blue().into(),
|
||||
color.alpha().into(),
|
||||
);
|
||||
|
||||
// If the output port is farther right than the input port and they have
|
||||
// a similar y coordinate, apply a y offset to the control points
|
||||
// so that the curve sticks out a bit.
|
||||
@@ -551,7 +578,7 @@ mod imp {
|
||||
};
|
||||
}
|
||||
|
||||
fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context) {
|
||||
fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context, colors: &Colors) {
|
||||
let Some(port_anchor) = port.compute_point(&*self.obj(), &port.link_anchor()) else {
|
||||
return;
|
||||
};
|
||||
@@ -579,7 +606,9 @@ mod imp {
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
self.draw_link(link_cr, output_anchor, input_anchor, false);
|
||||
let color = &colors.color_for_media_type(MediaType::from_raw(port.media_type()));
|
||||
|
||||
self.draw_link(link_cr, output_anchor, input_anchor, false, color);
|
||||
}
|
||||
|
||||
fn snapshot_links(&self, widget: &super::GraphView, snapshot: >k::Snapshot) {
|
||||
@@ -594,30 +623,45 @@ mod imp {
|
||||
|
||||
link_cr.set_line_width(2.0 * self.zoom_factor.get());
|
||||
|
||||
let rgba = widget
|
||||
.style_context()
|
||||
.lookup_color("graphview-link")
|
||||
.unwrap_or(gtk::gdk::RGBA::BLACK);
|
||||
|
||||
link_cr.set_source_rgba(
|
||||
rgba.red().into(),
|
||||
rgba.green().into(),
|
||||
rgba.blue().into(),
|
||||
rgba.alpha().into(),
|
||||
);
|
||||
let colors = Colors {
|
||||
audio: widget
|
||||
.style_context()
|
||||
.lookup_color("media-type-audio")
|
||||
.expect("color not found"),
|
||||
video: widget
|
||||
.style_context()
|
||||
.lookup_color("media-type-video")
|
||||
.expect("color not found"),
|
||||
midi: widget
|
||||
.style_context()
|
||||
.lookup_color("media-type-midi")
|
||||
.expect("color not found"),
|
||||
unknown: widget
|
||||
.style_context()
|
||||
.lookup_color("media-type-unknown")
|
||||
.expect("color not found"),
|
||||
};
|
||||
|
||||
for link in self.links.borrow().iter() {
|
||||
let color = &colors.color_for_media_type(link.media_type());
|
||||
|
||||
// TODO: Do not draw links when they are outside the view
|
||||
let Some((output_anchor, input_anchor)) = self.get_link_coordinates(link) else {
|
||||
warn!("Could not get allocation of ports of link: {:?}", link);
|
||||
continue;
|
||||
};
|
||||
|
||||
self.draw_link(&link_cr, &output_anchor, &input_anchor, link.active());
|
||||
self.draw_link(
|
||||
&link_cr,
|
||||
&output_anchor,
|
||||
&input_anchor,
|
||||
link.active(),
|
||||
color,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(port) = self.dragged_port.upgrade() {
|
||||
self.draw_dragged_link(&port, &link_cr);
|
||||
self.draw_dragged_link(&port, &link_cr, &colors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -793,6 +837,12 @@ impl GraphView {
|
||||
graph.queue_draw();
|
||||
}),
|
||||
);
|
||||
link.connect_notify_local(
|
||||
Some("media-type"),
|
||||
glib::clone!(@weak self as graph => move |_, _| {
|
||||
graph.queue_draw();
|
||||
}),
|
||||
);
|
||||
self.imp().links.borrow_mut().insert(link);
|
||||
self.queue_draw();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::format::MediaType;
|
||||
|
||||
use super::Port;
|
||||
|
||||
@@ -25,11 +26,22 @@ mod imp {
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Link {
|
||||
pub output_port: glib::WeakRef<Port>,
|
||||
pub input_port: glib::WeakRef<Port>,
|
||||
pub active: Cell<bool>,
|
||||
pub media_type: Cell<MediaType>,
|
||||
}
|
||||
|
||||
impl Default for Link {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
output_port: glib::WeakRef::default(),
|
||||
input_port: glib::WeakRef::default(),
|
||||
active: Cell::default(),
|
||||
media_type: Cell::new(MediaType::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
@@ -53,6 +65,10 @@ mod imp {
|
||||
.default_value(false)
|
||||
.flags(glib::ParamFlags::READWRITE)
|
||||
.build(),
|
||||
glib::ParamSpecUInt::builder("media-type")
|
||||
.default_value(MediaType::Unknown.as_raw())
|
||||
.flags(glib::ParamFlags::READWRITE)
|
||||
.build(),
|
||||
]
|
||||
});
|
||||
|
||||
@@ -64,6 +80,7 @@ mod imp {
|
||||
"output-port" => self.output_port.upgrade().to_value(),
|
||||
"input-port" => self.input_port.upgrade().to_value(),
|
||||
"active" => self.active.get().to_value(),
|
||||
"media-type" => self.media_type.get().as_raw().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
@@ -73,6 +90,9 @@ mod imp {
|
||||
"output-port" => self.output_port.set(value.get().unwrap()),
|
||||
"input-port" => self.input_port.set(value.get().unwrap()),
|
||||
"active" => self.active.set(value.get().unwrap()),
|
||||
"media-type" => self
|
||||
.media_type
|
||||
.set(MediaType::from_raw(value.get().unwrap())),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
@@ -111,6 +131,14 @@ impl Link {
|
||||
pub fn set_active(&self, active: bool) {
|
||||
self.set_property("active", active);
|
||||
}
|
||||
|
||||
pub fn media_type(&self) -> MediaType {
|
||||
MediaType::from_raw(self.property("media-type"))
|
||||
}
|
||||
|
||||
pub fn set_media_type(&self, media_type: MediaType) {
|
||||
self.set_property("media-type", media_type.as_raw())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Link {
|
||||
|
||||
@@ -23,20 +23,26 @@ use gtk::{
|
||||
};
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
use crate::MediaType;
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
use pipewire::spa::Direction;
|
||||
use pipewire::spa::{format::MediaType, Direction};
|
||||
|
||||
/// Graphical representation of a pipewire port.
|
||||
#[derive(Default, glib::Properties)]
|
||||
#[derive(glib::Properties)]
|
||||
#[properties(wrapper_type = super::Port)]
|
||||
pub struct Port {
|
||||
#[property(get, set, construct_only)]
|
||||
pub(super) pipewire_id: OnceCell<u32>,
|
||||
#[property(
|
||||
type = u32,
|
||||
get = |_| self.media_type.get().as_raw(),
|
||||
set = Self::set_media_type
|
||||
)]
|
||||
pub(super) media_type: Cell<MediaType>,
|
||||
#[property(
|
||||
name = "name", type = String,
|
||||
get = |this: &Self| this.label.text().to_string(),
|
||||
@@ -49,6 +55,17 @@ mod imp {
|
||||
pub(super) direction: OnceCell<Direction>,
|
||||
}
|
||||
|
||||
impl Default for Port {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
pipewire_id: OnceCell::default(),
|
||||
media_type: Cell::new(MediaType::Unknown),
|
||||
label: gtk::Label::default(),
|
||||
direction: OnceCell::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for Port {
|
||||
const NAME: &'static str = "HelvumPort";
|
||||
@@ -184,6 +201,26 @@ mod imp {
|
||||
obj.add_controller(drop_target);
|
||||
}
|
||||
}
|
||||
|
||||
impl Port {
|
||||
fn set_media_type(&self, media_type: u32) {
|
||||
let media_type = MediaType::from_raw(media_type);
|
||||
|
||||
self.media_type.set(media_type);
|
||||
|
||||
for css_class in ["video", "audio", "midi"] {
|
||||
self.obj().remove_css_class(css_class)
|
||||
}
|
||||
|
||||
// Color the port according to its media type.
|
||||
match media_type {
|
||||
MediaType::Video => self.obj().add_css_class("video"),
|
||||
MediaType::Audio => self.obj().add_css_class("audio"),
|
||||
MediaType::Application | MediaType::Stream => self.obj().add_css_class("midi"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
@@ -192,7 +229,7 @@ glib::wrapper! {
|
||||
}
|
||||
|
||||
impl Port {
|
||||
pub fn new(id: u32, name: &str, direction: Direction, media_type: Option<MediaType>) -> Self {
|
||||
pub fn new(id: u32, name: &str, direction: Direction) -> Self {
|
||||
// Create the widget and initialize needed fields
|
||||
let res: Self = glib::Object::builder()
|
||||
.property("pipewire-id", id)
|
||||
@@ -208,14 +245,6 @@ impl Port {
|
||||
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port.
|
||||
res.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
|
||||
|
||||
// Color the port according to its media type.
|
||||
match media_type {
|
||||
Some(MediaType::Video) => res.add_css_class("video"),
|
||||
Some(MediaType::Audio) => res.add_css_class("audio"),
|
||||
Some(MediaType::Midi) => res.add_css_class("midi"),
|
||||
None => {}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user