Skip to content

Commit 7b42058

Browse files
committed
fix(sound): use libpulse to monitor active port changes
1 parent 15d29d1 commit 7b42058

File tree

2 files changed

+152
-12
lines changed

2 files changed

+152
-12
lines changed

subscriptions/sound/src/lib.rs

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ pub struct Model {
164164
source_node_ids: Vec<NodeId>,
165165
/// Index of active source device.
166166
active_source: Option<usize>,
167-
/// Device ID of active source device.
167+
/// Node ID of active source device.
168168
active_source_node: Option<u32>,
169-
/// Device identifier of the default source.
169+
/// Node identifier of the default source.
170170
active_source_node_name: String,
171171

172172
pub sink_volume_text: String,
@@ -369,6 +369,80 @@ impl Model {
369369
numtoa::BaseN::<10>::u32(volume).as_str().to_owned();
370370
}
371371

372+
pulse::Event::SourcePortChange(name, availability) => {
373+
let Some(node_id) = self.active_source_node else {
374+
continue;
375+
};
376+
377+
let Some(device_id) = self.device_ids.get(node_id).cloned() else {
378+
continue;
379+
};
380+
381+
let Some(routes) = self.device_routes.get_mut(device_id) else {
382+
continue;
383+
};
384+
385+
let mut description = None;
386+
387+
for route in routes {
388+
if route.name == name {
389+
route.available = availability;
390+
description = Some(route.description.clone());
391+
}
392+
}
393+
394+
if !matches!(availability, Availability::No) {
395+
if let Some(description) = description {
396+
if let Some((name, _)) = self.route_name_get(
397+
&description,
398+
availability,
399+
device_id,
400+
) {
401+
if let Some(pos) = self.active_source {
402+
self.sources[pos] = name;
403+
}
404+
}
405+
}
406+
}
407+
}
408+
409+
pulse::Event::SinkPortChange(name, availability) => {
410+
let Some(node_id) = self.active_sink_node else {
411+
continue;
412+
};
413+
414+
let Some(device_id) = self.device_ids.get(node_id).cloned() else {
415+
continue;
416+
};
417+
418+
let Some(routes) = self.device_routes.get_mut(device_id) else {
419+
continue;
420+
};
421+
422+
let mut description = None;
423+
424+
for route in routes {
425+
if route.name == name {
426+
route.available = availability;
427+
description = Some(route.description.clone());
428+
}
429+
}
430+
431+
if !matches!(availability, Availability::No) {
432+
if let Some(description) = description {
433+
if let Some((name, _)) = self.route_name_get(
434+
&description,
435+
availability,
436+
device_id,
437+
) {
438+
if let Some(pos) = self.active_sink {
439+
self.sinks[pos] = name;
440+
}
441+
}
442+
}
443+
}
444+
}
445+
372446
pulse::Event::DefaultSink(node_name) => {
373447
if self.active_sink_node_name == node_name {
374448
continue;
@@ -466,7 +540,7 @@ impl Model {
466540
});
467541
}
468542

469-
pipewire::Event::ActiveRoute(id, index, route) => {
543+
pipewire::Event::ActiveRoute(id, _index, route) => {
470544
self.update_device_route(&route, id);
471545
}
472546

@@ -540,7 +614,7 @@ impl Model {
540614
}
541615
}
542616
}
543-
};
617+
}
544618

545619
self.node_descriptions.insert(node.object_id, description);
546620

@@ -632,16 +706,30 @@ impl Model {
632706
return None;
633707
}
634708

709+
let (name, plugged) = self.route_name_get(&route.description, route.available, device)?;
710+
711+
if plugged {
712+
self.node_route_plugged.insert(node, ());
713+
}
714+
715+
Some(name)
716+
}
717+
718+
fn route_name_get(
719+
&self,
720+
route_description: &str,
721+
route_available: Availability,
722+
device: DeviceId,
723+
) -> Option<(String, bool)> {
635724
let device_name = self.device_names.get(device)?;
636725

637-
let port_name = if matches!(route.available, Availability::No) {
638-
&self.unplugged_text
726+
let (port_name, plugged) = if matches!(route_available, Availability::No) {
727+
(self.unplugged_text.as_str(), false)
639728
} else {
640-
self.node_route_plugged.insert(node, ());
641-
&route.description
729+
(route_description, true)
642730
};
643731

644-
Some([&port_name, " - ", device_name].concat())
732+
Some(([&port_name, " - ", device_name].concat(), plugged))
645733
}
646734

647735
fn update_device_route(&mut self, route: &pipewire::Route, id: DeviceId) {

subscriptions/sound/src/pulse.rs

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ use std::{
3333
sync::mpsc,
3434
};
3535

36+
use crate::pipewire::Availability;
37+
3638
pub fn subscription() -> iced_futures::Subscription<Event> {
3739
Subscription::run_with_id(
3840
"pulse",
@@ -59,8 +61,10 @@ pub fn thread(sender: futures::channel::mpsc::Sender<Event>) {
5961
_inner: Rc::clone(&main_loop._inner),
6062
}),
6163
introspector: context.introspect(),
64+
sink_port: RefCell::new(None),
6265
sink_volume: Cell::new(None),
6366
sink_mute: Cell::new(None),
67+
source_port: RefCell::new(None),
6468
source_volume: Cell::new(None),
6569
source_mute: Cell::new(None),
6670
default_sink_name: RefCell::new(None),
@@ -109,13 +113,15 @@ pub fn thread(sender: futures::channel::mpsc::Sender<Event>) {
109113
#[derive(Clone, Debug)]
110114
pub enum Event {
111115
Balance(Option<f32>),
116+
Channels(PulseChannels),
112117
DefaultSink(String),
113118
DefaultSource(String),
114-
SinkVolume(u32),
115-
Channels(PulseChannels),
119+
SinkPortChange(String, Availability),
116120
SinkMute(bool),
117-
SourceVolume(u32),
121+
SinkVolume(u32),
122+
SourcePortChange(String, Availability),
118123
SourceMute(bool),
124+
SourceVolume(u32),
119125
}
120126

121127
enum Request {
@@ -305,8 +311,10 @@ struct Data {
305311
main_loop: RefCell<Mainloop>,
306312
default_sink_name: RefCell<Option<String>>,
307313
default_source_name: RefCell<Option<String>>,
314+
sink_port: RefCell<Option<String>>,
308315
sink_volume: Cell<Option<u32>>,
309316
sink_mute: Cell<Option<bool>>,
317+
source_port: RefCell<Option<String>>,
310318
source_volume: Cell<Option<u32>>,
311319
source_mute: Cell<Option<bool>>,
312320
introspector: Introspector,
@@ -361,6 +369,28 @@ impl Data {
361369
if sink_info.name.as_deref() != self.default_sink_name.borrow().as_deref() {
362370
return;
363371
}
372+
373+
if let Some(port) = sink_info.active_port.as_deref() {
374+
let port_name = port.name.as_deref();
375+
if self.sink_port.borrow().as_deref() != port_name {
376+
*self.sink_port.borrow_mut() = port_name.map(str::to_owned);
377+
if let Some(name) = port_name {
378+
if block_on(self.sender.borrow_mut().send(Event::SinkPortChange(
379+
name.to_owned(),
380+
match port.available {
381+
libpulse_binding::def::PortAvailable::No => Availability::No,
382+
libpulse_binding::def::PortAvailable::Yes => Availability::Yes,
383+
_ => Availability::Unknown,
384+
},
385+
)))
386+
.is_err()
387+
{
388+
self.main_loop.borrow_mut().quit(Retval(0));
389+
}
390+
}
391+
}
392+
}
393+
364394
let balance = (sink_info.channel_map.can_balance()
365395
&& sink_info.base_volume.is_normal())
366396
.then(|| sink_info.volume.get_balance(&sink_info.channel_map));
@@ -412,6 +442,28 @@ impl Data {
412442
if source_info.name.as_deref() != self.default_source_name.borrow().as_deref() {
413443
return;
414444
}
445+
446+
if let Some(port) = source_info.active_port.as_deref() {
447+
let port_name = port.name.as_deref();
448+
if self.source_port.borrow().as_deref() != port_name {
449+
*self.source_port.borrow_mut() = port_name.map(str::to_owned);
450+
if let Some(name) = port_name {
451+
if block_on(self.sender.borrow_mut().send(Event::SourcePortChange(
452+
name.to_owned(),
453+
match port.available {
454+
libpulse_binding::def::PortAvailable::No => Availability::No,
455+
libpulse_binding::def::PortAvailable::Yes => Availability::Yes,
456+
_ => Availability::Unknown,
457+
},
458+
)))
459+
.is_err()
460+
{
461+
self.main_loop.borrow_mut().quit(Retval(0));
462+
}
463+
}
464+
}
465+
}
466+
415467
let volume = source_info.volume.max().0 / (Volume::NORMAL.0 / 100);
416468
if self.source_mute.get() != Some(source_info.mute) {
417469
self.source_mute.set(Some(source_info.mute));

0 commit comments

Comments
 (0)