// Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 use crate::Channel; use crate::spa_utils::array_from_pod; use libspa::pod::Pod; use libspa::utils::Id; use pipewire::node::{NodeInfoRef, NodeState}; use std::ffi::c_float; /// Node information #[must_use] #[derive(Clone, Debug)] pub struct Node { pub object_id: u32, pub audio_channels: u32, pub audio_position: String, pub card_profile_device: Option, pub description: String, pub device_id: Option, pub device_profile_description: String, pub device_profile_pro: bool, pub icon_name: String, pub media_class: MediaClass, pub node_name: String, pub state: State, } #[derive(Clone, Debug, PartialEq, Eq)] pub enum State { Idle, Running, Creating, Suspended, Error(String), } #[derive(Clone, Debug, PartialEq, Eq)] pub enum MediaClass { Source, Sink, } impl Node { /// Attains process info from a pipewire info node. #[must_use] pub fn from_node(info: &NodeInfoRef) -> Option { let props = info.props()?; let mut audio_channels = 1; let mut audio_position = String::new(); let mut card_profile_device = None; let mut device_id = None; let mut device_profile_description: &str = ""; let mut device_profile_pro = false; let mut icon_name = String::new(); let mut media_class = None; let mut node_description: &str = ""; let mut node_name = String::new(); let mut object_id = None; for (entry, value) in props.iter() { match entry { "device.id" => device_id = value.parse::().ok(), "object.id" => object_id = Some(value.parse::().ok()?), // 2 "audio.channels" => audio_channels = value.parse::().unwrap_or(1), // FL,FR "audio.position" => audio_position = value.to_owned(), // 0 "card.profile.device" => card_profile_device = Some(value.parse::().ok()?), // Analog Stereo (ALSA only) "device.profile.description" => { device_profile_description = value; } // false "device.profile.pro" => device_profile_pro = value == "true", // audio-card-analog "device.icon-name" => icon_name = value.to_owned(), "media.class" => { media_class = Some(match value { "Audio/Sink" => MediaClass::Sink, "Audio/Source" => MediaClass::Source, _ => return None, }) } // alsa_input.pci-0000_66_00.6.analog-stereo "node.name" => node_name = value.to_owned(), // Family 17h/19h HD Audio Controller Analog Stereo "node.description" => node_description = value, _ => (), } } let device = Node { object_id: object_id?, device_id, card_profile_device, media_class: media_class?, description: if device_profile_description.is_empty() { node_description.to_owned() } else { let device_name = node_description .strip_suffix(device_profile_description) .unwrap_or(node_description) .trim_ascii_end(); device_name.to_owned() }, device_profile_description: device_profile_description.to_owned(), device_profile_pro, icon_name, audio_channels, audio_position, node_name, state: match info.state() { NodeState::Idle => State::Idle, NodeState::Running => State::Running, NodeState::Creating => State::Creating, NodeState::Suspended => State::Suspended, NodeState::Error(why) => State::Error(why.to_owned()), }, }; Some(device) } } #[derive(Clone, Debug, Default)] pub struct NodeProps { pub mute: Option, pub monitor_mute: Option, pub channel_map: Option>, pub channel_volumes: Option>, } impl NodeProps { pub fn from_pod(pod: &Pod) -> Option { let props = pod.as_object().ok()?; let props = NodeProps { mute: props .find_prop(Id(libspa_sys::SPA_PROP_mute)) .and_then(|prop| prop.value().get_bool().ok()), monitor_mute: props .find_prop(Id(libspa_sys::SPA_PROP_monitorMute)) .and_then(|prop| prop.value().get_bool().ok()), channel_map: props .find_prop(Id(libspa_sys::SPA_PROP_channelMap)) .and_then(|prop| unsafe { array_from_pod::(prop.value()) }), channel_volumes: props .find_prop(Id(libspa_sys::SPA_PROP_channelVolumes)) .and_then(|prop| unsafe { array_from_pod::(prop.value()) }), }; if props.mute.is_none() && props.monitor_mute.is_none() && props.channel_map.is_none() && props.channel_volumes.is_none() { None } else { Some(props) } } pub fn merge(&mut self, other: NodeProps) { if other.mute.is_some() { self.mute = other.mute } if other.monitor_mute.is_some() { self.monitor_mute = other.monitor_mute; } if other.channel_map.is_some() { self.channel_map = other.channel_map; } if other.channel_volumes.is_some() { self.channel_volumes = other.channel_volumes; } } }