feat: merge subscriptions crate into cosmic-settings repo
This commit is contained in:
parent
a2f53f2239
commit
600720b7d1
47 changed files with 8399 additions and 63 deletions
279
subscriptions/sound/src/pipewire.rs
Normal file
279
subscriptions/sound/src/pipewire.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
// Copyright 2024 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// #![deny(missing_docs)]
|
||||
|
||||
pub use pipewire::channel::Sender;
|
||||
|
||||
use cosmic::iced_futures::{self, Subscription, stream};
|
||||
use futures::{SinkExt, executor::block_on};
|
||||
use pipewire::{
|
||||
context::Context as PwContext,
|
||||
main_loop::MainLoop as PwMainLoop,
|
||||
node::{Node, NodeInfoRef, NodeState},
|
||||
proxy::{Listener, ProxyT},
|
||||
types::ObjectType,
|
||||
};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{BTreeMap, HashMap},
|
||||
rc::Rc,
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
pub fn subscription() -> iced_futures::Subscription<DeviceEvent> {
|
||||
Subscription::run_with_id(
|
||||
"pipewire",
|
||||
stream::channel(20, |sender| async {
|
||||
_ = thread(sender);
|
||||
|
||||
futures::future::pending().await
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn thread(
|
||||
on_event: futures::channel::mpsc::Sender<DeviceEvent>,
|
||||
) -> (JoinHandle<()>, pipewire::channel::Sender<()>) {
|
||||
let (pw_tx, pw_rx) = pipewire::channel::channel();
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
devices_from_socket(pw_rx, on_event);
|
||||
});
|
||||
|
||||
(handle, pw_tx)
|
||||
}
|
||||
|
||||
/// Node event`
|
||||
#[derive(Debug)]
|
||||
pub enum NodeEvent<'a> {
|
||||
/// Node info
|
||||
NodeInfo(u32, &'a NodeInfoRef),
|
||||
/// Node removal
|
||||
Remove(u32),
|
||||
}
|
||||
|
||||
/// Device event
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum DeviceEvent {
|
||||
/// A new device was detected.
|
||||
Add(Device),
|
||||
/// A device with the given object_id was removed.
|
||||
Remove(u32),
|
||||
}
|
||||
|
||||
/// Device information
|
||||
#[must_use]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Device {
|
||||
pub object_id: u32,
|
||||
pub variant: DeviceVariant,
|
||||
pub media_class: MediaClass,
|
||||
pub product_name: String,
|
||||
pub node_name: String,
|
||||
pub state: DeviceState,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
|
||||
pub enum DeviceVariant {
|
||||
Alsa { alsa_card: u32 },
|
||||
Bluez5 { address: String },
|
||||
Unknown {},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum DeviceState {
|
||||
Idle,
|
||||
Running,
|
||||
Creating,
|
||||
Suspended,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum MediaClass {
|
||||
Source,
|
||||
Sink,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Attains process info from a pipewire info node.
|
||||
#[must_use]
|
||||
pub fn from_node(info: &NodeInfoRef) -> Option<Self> {
|
||||
let props = info.props()?;
|
||||
|
||||
let (variant, product_name) = if let Some(alsa_card) =
|
||||
props.get("alsa.card").and_then(|v| v.parse::<u32>().ok())
|
||||
{
|
||||
let device_profile_description = props.get("device.profile.description")?.to_owned();
|
||||
|
||||
let description = props.get("node.description")?;
|
||||
|
||||
let description = description
|
||||
.strip_suffix(&device_profile_description)
|
||||
.map(str::trim_end)
|
||||
.unwrap_or(description)
|
||||
.replace("High Definition Audio", "HD Audio");
|
||||
|
||||
(DeviceVariant::Alsa { alsa_card }, description)
|
||||
} else if let Some(address) = props
|
||||
.get("api.bluez5.address")
|
||||
.and_then(|v| v.parse::<String>().ok())
|
||||
{
|
||||
(
|
||||
DeviceVariant::Bluez5 {
|
||||
address: address.to_owned(),
|
||||
},
|
||||
props.get("node.description")?.to_owned(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
DeviceVariant::Unknown {},
|
||||
props.get("node.description")?.to_owned(),
|
||||
)
|
||||
};
|
||||
|
||||
Some(Device {
|
||||
object_id: props.get("object.id")?.parse::<u32>().ok()?,
|
||||
variant,
|
||||
media_class: match props.get("media.class")? {
|
||||
"Audio/Sink" => MediaClass::Sink,
|
||||
"Audio/Source" => MediaClass::Source,
|
||||
_ => return None,
|
||||
},
|
||||
product_name,
|
||||
node_name: props.get("node.name")?.to_owned(),
|
||||
state: match info.state() {
|
||||
NodeState::Idle => DeviceState::Idle,
|
||||
NodeState::Running => DeviceState::Running,
|
||||
NodeState::Creating => DeviceState::Creating,
|
||||
NodeState::Suspended => DeviceState::Suspended,
|
||||
NodeState::Error(why) => DeviceState::Error(why.to_owned()),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Monitors the devices from a given ``PipeWire`` socket.
|
||||
///
|
||||
/// ``PipeWire`` sockets are found in `/run/user/{{UID}}/pipewire-0`.
|
||||
pub fn devices_from_socket(
|
||||
pw_cancel: pipewire::channel::Receiver<()>,
|
||||
mut on_event: futures::channel::mpsc::Sender<DeviceEvent>,
|
||||
) {
|
||||
let mut managed = BTreeMap::new();
|
||||
|
||||
let _res = nodes_from_socket(pw_cancel, move |main_loop, event| match event {
|
||||
NodeEvent::NodeInfo(pw_id, info) => {
|
||||
if let Some(device) = Device::from_node(info) {
|
||||
if managed.insert(pw_id, device.object_id).is_none() {
|
||||
if block_on(on_event.send(DeviceEvent::Add(device))).is_err() {
|
||||
main_loop.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeEvent::Remove(pw_id) => {
|
||||
if let Some(object_id) = managed.remove(&pw_id) {
|
||||
if block_on(on_event.send(DeviceEvent::Remove(object_id))).is_err() {
|
||||
main_loop.quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Listens to information about nodes, passing that info into a callback.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Errors if the pipewire connection fails
|
||||
pub fn nodes_from_socket(
|
||||
pw_cancel: pipewire::channel::Receiver<()>,
|
||||
on_event: impl FnMut(&PwMainLoop, NodeEvent) + 'static,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let main_loop = PwMainLoop::new(None)?;
|
||||
let context = PwContext::new(&main_loop)?;
|
||||
let core = context.connect(None)?;
|
||||
|
||||
// Exit main loop on receivering terminate message.
|
||||
let _cancel_rx = pw_cancel.attach(main_loop.loop_(), {
|
||||
let main_loop = main_loop.clone();
|
||||
move |_| main_loop.quit()
|
||||
});
|
||||
|
||||
let registry = Rc::new(core.get_registry()?);
|
||||
let registry_weak = Rc::downgrade(®istry);
|
||||
|
||||
let proxies = Rc::new(RefCell::new(HashMap::new()));
|
||||
let on_event = Rc::new(RefCell::new(on_event));
|
||||
|
||||
let main_loop_clone = main_loop.clone();
|
||||
|
||||
let _registry_listener = registry
|
||||
.add_listener_local()
|
||||
.global(move |obj| {
|
||||
let Some(registry) = registry_weak.upgrade() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let attached_proxy: Option<(Box<dyn ProxyT>, Box<dyn Listener>)> = match obj.type_ {
|
||||
ObjectType::Node => {
|
||||
let Ok(node): Result<Node, _> = registry.bind(obj) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let on_event_weak = Rc::downgrade(&on_event);
|
||||
let main_loop = main_loop_clone.clone();
|
||||
let id = node.upcast_ref().id();
|
||||
|
||||
let listener = node
|
||||
.add_listener_local()
|
||||
.info(move |info| {
|
||||
if let Some(on_event) = on_event_weak.upgrade() {
|
||||
on_event.borrow_mut()(&main_loop, NodeEvent::NodeInfo(id, info));
|
||||
}
|
||||
})
|
||||
.register();
|
||||
|
||||
Some((Box::new(node), Box::new(listener)))
|
||||
}
|
||||
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((proxy_spe, listener)) = attached_proxy {
|
||||
let proxy = proxy_spe.upcast_ref();
|
||||
let id = proxy.id();
|
||||
let (object_type, _object_version) = proxy.get_type();
|
||||
|
||||
let proxies_weak = Rc::downgrade(&proxies);
|
||||
let on_event_weak = Rc::downgrade(&on_event);
|
||||
let main_loop = main_loop_clone.clone();
|
||||
|
||||
let remove_listener = proxy
|
||||
.add_listener_local()
|
||||
.removed(move || {
|
||||
if object_type == ObjectType::Node {
|
||||
if let Some(on_event) = on_event_weak.upgrade() {
|
||||
on_event.borrow_mut()(&main_loop, NodeEvent::Remove(id));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(proxies) = proxies_weak.upgrade() {
|
||||
proxies.borrow_mut().remove(&id);
|
||||
}
|
||||
})
|
||||
.register();
|
||||
|
||||
proxies
|
||||
.borrow_mut()
|
||||
.insert(id, (proxy_spe, listener, remove_listener));
|
||||
}
|
||||
})
|
||||
.register();
|
||||
|
||||
main_loop.run();
|
||||
Ok(())
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue