1use std::cell::{Cell, OnceCell, RefCell};
11use std::rc::Rc;
12use std::sync::{Arc, Once};
13use std::time::{Duration, Instant};
14
15use calloop::channel::Sender;
16use calloop::timer::TimeoutAction;
17use crabshell_reactive::{Owner, ReadSignal, Signal};
18use crabshell_ui::Cx;
19use libpulse_binding::callbacks::ListResult;
20use libpulse_binding::channelmap::{Map, Position};
21use libpulse_binding::context::introspect::{
22 Introspector, ServerInfo, SinkInfo, SinkInputInfo, SourceInfo,
23};
24use libpulse_binding::context::subscribe::{Facility, InterestMaskSet};
25use libpulse_binding::context::{Context, FlagSet, State};
26use libpulse_binding::def::SinkState;
27use libpulse_binding::mainloop::threaded::Mainloop;
28use libpulse_binding::volume::{ChannelVolumes, Volume};
29
30use crate::Shared;
31use crate::dbus::set_changed;
32
33mod peaks;
34#[cfg(test)]
35mod tests;
36
37pub use peaks::PeakTarget;
38
39pub(crate) const RETRY: Duration = Duration::from_secs(2);
41
42pub(crate) const RETRY_FOR: Duration = Duration::from_secs(60);
45
46pub(crate) const SETTLE: Duration = Duration::from_millis(250);
48
49const MAX_NODES: usize = 128;
51
52pub(crate) struct Server {
54 pub(crate) address: Option<String>,
57 pub(crate) retry: Duration,
59 pub(crate) retry_for: Duration,
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct Sink {
66 pub name: String,
69 pub volume_percent: u32,
72 pub muted: bool,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct AudioNode {
80 pub name: String,
82 pub description: String,
84 pub volume_percent: u32,
86 pub volumes: Vec<u32>,
89 pub channels: Vec<AudioChannel>,
92 pub muted: bool,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct AudioStream {
99 pub index: u32,
101 pub app_name: String,
103 pub title: String,
105 pub icon_name: String,
107 pub sink: String,
109 pub volume_percent: u32,
111 pub volumes: Vec<u32>,
113 pub channels: Vec<AudioChannel>,
115 pub muted: bool,
117}
118
119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
122#[allow(missing_docs, reason = "each name says where it plays")]
123pub enum AudioChannel {
124 Mono,
125 FrontLeft,
126 FrontRight,
127 FrontCenter,
128 RearCenter,
129 RearLeft,
130 RearRight,
131 LowFrequencyEffects,
132 FrontLeftCenter,
133 FrontRightCenter,
134 SideLeft,
135 SideRight,
136 Aux(u8),
138 TopCenter,
139 TopFrontLeft,
140 TopFrontRight,
141 TopFrontCenter,
142 TopRearLeft,
143 TopRearRight,
144 TopRearCenter,
145 Unknown,
147}
148
149#[derive(Clone)]
151pub struct Audio {
152 sink: ReadSignal<Option<Sink>>,
153 lists: ReadLists,
154 owner: Owner,
156 no_peaks: ReadSignal<Vec<f32>>,
158 service: Option<Rc<AudioService>>,
160}
161
162impl Audio {
163 pub fn default_sink(&self) -> ReadSignal<Option<Sink>> {
166 self.sink
167 }
168
169 pub fn change_volume(&self, percent: i32) {
175 self.command(|pulse| pulse.change_volume(percent));
176 }
177
178 pub fn toggle_mute(&self) {
181 self.command(Pulse::toggle_mute);
182 }
183
184 pub fn sinks(&self) -> ReadSignal<Vec<AudioNode>> {
186 self.lists.sinks
187 }
188
189 pub fn sources(&self) -> ReadSignal<Vec<AudioNode>> {
191 self.lists.sources
192 }
193
194 pub fn default_source(&self) -> ReadSignal<Option<AudioNode>> {
196 self.lists.default_source
197 }
198
199 pub fn streams(&self) -> ReadSignal<Vec<AudioStream>> {
201 self.lists.streams
202 }
203
204 pub fn set_default_sink(&self, name: &str) {
206 let name = name.to_owned();
207 self.command(move |pulse| pulse.set_default(Kind::Sink, &name));
208 }
209
210 pub fn set_default_source(&self, name: &str) {
212 let name = name.to_owned();
213 self.command(move |pulse| pulse.set_default(Kind::Source, &name));
214 }
215
216 pub fn set_sink_volume(&self, name: &str, percent: u32) {
219 let name = name.to_owned();
220 self.command(move |pulse| pulse.set_node_volume(Kind::Sink, &name, percent));
221 }
222
223 pub fn set_sink_volumes(&self, name: &str, percents: &[u32]) {
228 let (name, percents) = (name.to_owned(), percents.to_vec());
229 self.command(move |pulse| pulse.set_node_volumes(Kind::Sink, &name, &percents));
230 }
231
232 pub fn set_sink_mute(&self, name: &str, muted: bool) {
234 let name = name.to_owned();
235 self.command(move |pulse| pulse.set_node_mute(Kind::Sink, &name, muted));
236 }
237
238 pub fn set_source_volume(&self, name: &str, percent: u32) {
241 let name = name.to_owned();
242 self.command(move |pulse| pulse.set_node_volume(Kind::Source, &name, percent));
243 }
244
245 pub fn set_source_volumes(&self, name: &str, percents: &[u32]) {
248 let (name, percents) = (name.to_owned(), percents.to_vec());
249 self.command(move |pulse| pulse.set_node_volumes(Kind::Source, &name, &percents));
250 }
251
252 pub fn set_source_mute(&self, name: &str, muted: bool) {
255 let name = name.to_owned();
256 self.command(move |pulse| pulse.set_node_mute(Kind::Source, &name, muted));
257 }
258
259 pub fn set_stream_volume(&self, index: u32, percent: u32) {
262 self.command(move |pulse| pulse.set_stream_volume(index, percent));
263 }
264
265 pub fn set_stream_volumes(&self, index: u32, percents: &[u32]) {
268 let percents = percents.to_vec();
269 self.command(move |pulse| pulse.set_stream_volumes(index, &percents));
270 }
271
272 pub fn set_stream_mute(&self, index: u32, muted: bool) {
274 self.command(move |pulse| pulse.set_stream_mute(index, muted));
275 }
276
277 pub fn move_stream(&self, index: u32, sink: &str) {
279 let sink = sink.to_owned();
280 self.command(move |pulse| pulse.move_stream(index, &sink));
281 }
282
283 fn command(&self, command: impl FnOnce(&mut Pulse)) {
284 let mut pulse = self
285 .service
286 .as_ref()
287 .map(|service| service.pulse.borrow_mut());
288 match pulse.as_deref_mut().and_then(Option::as_mut) {
289 Some(pulse) => command(pulse),
290 None => tracing::debug!("no audio service: nothing to change"),
291 }
292 }
293}
294
295pub(crate) fn audio(cx: &Cx) -> Audio {
297 if let Some(service) = cx.service::<AudioService>() {
298 return service.audio(cx.owner());
299 }
300 static LOGGED: Once = Once::new();
301 LOGGED.call_once(|| {
302 tracing::warn!("cx.audio() needs the services the shell provides; there is no sink");
303 });
304 Audio {
305 sink: cx.signal(None).read_only(),
306 lists: Lists {
307 sinks: cx.signal(Vec::new()).read_only(),
308 sources: cx.signal(Vec::new()).read_only(),
309 streams: cx.signal(Vec::new()).read_only(),
310 default_source: cx.signal(None).read_only(),
311 },
312 owner: cx.owner(),
313 no_peaks: cx.signal(Vec::new()).read_only(),
314 service: None,
315 }
316}
317
318#[derive(Clone, Copy)]
320struct Lists<N, S, D> {
321 sinks: N,
322 sources: N,
323 streams: S,
324 default_source: D,
325}
326
327type ReadLists =
328 Lists<ReadSignal<Vec<AudioNode>>, ReadSignal<Vec<AudioStream>>, ReadSignal<Option<AudioNode>>>;
329
330type ListSignals =
331 Lists<Signal<Vec<AudioNode>>, Signal<Vec<AudioStream>>, Signal<Option<AudioNode>>>;
332
333impl ListSignals {
334 fn read_only(&self) -> ReadLists {
335 Lists {
336 sinks: self.sinks.read_only(),
337 sources: self.sources.read_only(),
338 streams: self.streams.read_only(),
339 default_source: self.default_source.read_only(),
340 }
341 }
342}
343
344#[derive(Clone, Copy, Debug)]
346enum Kind {
347 Sink,
348 Source,
349}
350
351pub(crate) struct AudioService {
353 shared: Rc<Shared>,
354 sink: OnceCell<Signal<Option<Sink>>>,
356 lists: OnceCell<ListSignals>,
358 pulse: RefCell<Option<Pulse>>,
361 meters: RefCell<Vec<peaks::Wanted>>,
363 next_meter: Cell<u64>,
364 no_peaks: OnceCell<Signal<Vec<f32>>>,
366}
367
368pub(crate) type Message = (u32, Event);
371
372pub(crate) enum Event {
373 State,
375 Server,
377 Sink(u32),
379 Default(Option<String>),
381 DefaultSource(Option<String>),
383 Source,
385 Stream,
387 Sinks(Vec<NodeInfo>),
389 Sources(Vec<NodeInfo>),
391 Streams(Vec<StreamInfo>),
393 Info(Option<Info>),
395 Peaks(u64),
397 MeterState(u64),
399 Closing,
401}
402
403impl AudioService {
404 pub(crate) fn new(shared: Rc<Shared>) -> AudioService {
405 AudioService {
406 shared,
407 sink: OnceCell::new(),
408 lists: OnceCell::new(),
409 pulse: RefCell::new(None),
410 meters: RefCell::new(Vec::new()),
411 next_meter: Cell::new(0),
412 no_peaks: OnceCell::new(),
413 }
414 }
415
416 fn audio(self: &Rc<Self>, owner: Owner) -> Audio {
417 let sink = *self.sink.get_or_init(|| {
418 self.after(Duration::ZERO, AudioService::start);
420 self.shared.root.signal(None)
421 });
422 let lists = *self.lists.get_or_init(|| {
423 let root = &self.shared.root;
424 Lists {
425 sinks: root.signal(Vec::new()),
426 sources: root.signal(Vec::new()),
427 streams: root.signal(Vec::new()),
428 default_source: root.signal(None),
429 }
430 });
431 let no_peaks = *self
432 .no_peaks
433 .get_or_init(|| self.shared.root.signal(Vec::new()));
434 Audio {
435 sink: sink.read_only(),
436 lists: lists.read_only(),
437 owner,
438 no_peaks: no_peaks.read_only(),
439 service: Some(Rc::clone(self)),
440 }
441 }
442
443 fn start(self: &Rc<Self>) {
446 let Some(server) = &self.shared.config.audio else {
447 return tracing::info!("no audio server is set, so there is no sink");
448 };
449 let sink = *self
450 .sink
451 .get()
452 .expect("the sink is made before libpulse starts");
453 let lists = *self
454 .lists
455 .get()
456 .expect("the lists are made before libpulse starts");
457 let Some(event_loop) = self.shared.event_loop() else {
458 return;
459 };
460 let Some(mut mainloop) = Mainloop::new() else {
461 tracing::error!("libpulse couldn't make its mainloop: no audio");
462 return;
463 };
464 if let Err(error) = mainloop.start() {
465 tracing::error!(%error, "libpulse's thread didn't start: no audio");
466 return;
467 }
468 let (sender, channel) = calloop::channel::channel();
469 let pulse = Pulse {
471 meters: Vec::new(),
472 closing: Vec::new(),
473 intro: None,
474 context: None,
475 mainloop,
476 sender: Arc::new(sender),
477 generation: 0,
478 shown: None,
479 asked: None,
480 sink,
481 lists,
482 nodes: Nodes::default(),
483 down: false,
484 retry_until: Instant::now() + server.retry_for,
485 };
486 let service = Rc::clone(self);
487 let on_message = Box::new(move |message| service.on_message(message));
488 if event_loop.insert_audio_channel(channel, on_message) {
489 *self.pulse.borrow_mut() = Some(pulse);
490 self.connect();
491 }
492 }
493
494 fn connect(self: &Rc<Self>) {
496 let mut pulse = self.pulse.borrow_mut();
497 let (Some(pulse), Some(server)) = (pulse.as_mut(), &self.shared.config.audio) else {
498 return;
499 };
500 if pulse.sink.try_with(|_| ()).is_none() {
502 return;
503 }
504 if let Err(why) = pulse.connect(server.address.as_deref()) {
505 self.lost(pulse, &why);
506 }
507 }
508
509 fn on_message(self: &Rc<Self>, (generation, event): Message) {
510 let mut pulse = self.pulse.borrow_mut();
511 let Some(pulse) = pulse
513 .as_mut()
514 .filter(|pulse| pulse.generation == generation)
515 else {
516 return;
517 };
518 match event {
519 Event::State => {
520 if let Err(why) = pulse.on_state() {
521 self.lost(pulse, &why);
522 }
523 }
524 Event::Server => {
525 pulse.read_default();
526 pulse.read_lists();
527 }
528 Event::Sink(index) => {
529 if pulse.shown.as_ref().is_some_and(|s| s.index == index) {
530 pulse.read_default();
531 }
532 pulse.read_sinks();
533 }
534 Event::Default(name) => pulse.read_sink(name),
535 Event::Info(info) => pulse.show(info),
536 Event::DefaultSource(name) => {
537 pulse.nodes.default_source = name;
538 pulse.show_lists();
539 }
540 Event::Source => pulse.read_sources(),
541 Event::Stream => pulse.read_streams(),
542 Event::Sinks(sinks) => {
543 pulse.nodes.sinks = sinks;
544 self.show_lists(pulse);
545 }
546 Event::Sources(sources) => {
547 pulse.nodes.sources = sources;
548 self.show_lists(pulse);
549 }
550 Event::Streams(streams) => {
551 pulse.nodes.streams = streams;
552 self.show_lists(pulse);
553 }
554 Event::Peaks(id) => pulse.read_peaks(id),
555 Event::MeterState(id) => pulse.on_meter_state(id),
556 Event::Closing => pulse.on_closing(),
557 }
558 }
559
560 fn show_lists(&self, pulse: &mut Pulse) {
562 pulse.show_lists();
563 pulse.sync_meters(&self.meters.borrow());
564 }
565
566 fn lost(self: &Rc<Self>, pulse: &mut Pulse, why: &str) {
571 let Some(Server {
572 retry, retry_for, ..
573 }) = self.shared.config.audio
574 else {
575 return;
576 };
577 let now = Instant::now();
578 if pulse.intro.is_some() {
580 pulse.retry_until = now + retry_for;
581 }
582 if pulse.disconnect() {
583 tracing::warn!(
584 reason = why,
585 "no audio server, trying again every {retry:?} for up to {retry_for:?}"
586 );
587 }
588 if now + retry > pulse.retry_until {
589 return tracing::info!(
591 "stopped trying to reach the audio server; it is tried again when the shell restarts"
592 );
593 }
594 self.after(retry, AudioService::connect);
595 }
596
597 fn after(self: &Rc<Self>, delay: Duration, f: fn(&Rc<AudioService>)) {
599 let Some(event_loop) = self.shared.event_loop() else {
600 return;
601 };
602 let service = Rc::clone(self);
603 let once = move || {
604 f(&service);
605 TimeoutAction::Drop
606 };
607 event_loop.insert_timer(Instant::now() + delay, Box::new(once));
608 }
609}
610
611struct Pulse {
615 meters: Vec<peaks::Meter>,
618 closing: Vec<peaks::Recording>,
620 intro: Option<Introspector>,
621 context: Option<Context>,
622 mainloop: Mainloop,
623 sender: Arc<Sender<Message>>,
627 generation: u32,
630 shown: Option<Info>,
632 asked: Option<(Info, Instant)>,
637 sink: Signal<Option<Sink>>,
638 lists: ListSignals,
639 nodes: Nodes,
641 down: bool,
643 retry_until: Instant,
645}
646
647#[derive(Default)]
649struct Nodes {
650 sinks: Vec<NodeInfo>,
651 sources: Vec<NodeInfo>,
652 streams: Vec<StreamInfo>,
653 default_source: Option<String>,
654}
655
656#[derive(Clone, PartialEq)]
658pub(crate) struct NodeInfo {
659 index: u32,
660 name: String,
661 description: String,
662 volumes: ChannelVolumes,
663 map: Map,
664 muted: bool,
665 monitor: bool,
667 monitor_source: Option<String>,
669 running: bool,
671}
672
673impl NodeInfo {
674 fn node(&self) -> AudioNode {
675 let (volumes, channels) = each_channel(&self.volumes, &self.map);
676 AudioNode {
677 name: self.name.clone(),
678 description: self.description.clone(),
679 volume_percent: percent(self.volumes.avg()),
680 volumes,
681 channels,
682 muted: self.muted,
683 }
684 }
685}
686
687#[derive(Clone, PartialEq)]
689pub(crate) struct StreamInfo {
690 index: u32,
691 app_name: String,
692 title: String,
693 icon_name: String,
694 sink: u32,
695 volumes: ChannelVolumes,
696 map: Map,
697 muted: bool,
698 corked: bool,
700}
701
702impl StreamInfo {
703 fn read(info: &SinkInputInfo) -> StreamInfo {
704 use libpulse_binding::proplist::properties::{APPLICATION_ICON_NAME, APPLICATION_NAME};
705
706 let property = |key| info.proplist.get_str(key).unwrap_or_default();
707 StreamInfo {
708 index: info.index,
709 app_name: property(APPLICATION_NAME),
710 title: info.name.as_deref().unwrap_or_default().to_owned(),
711 icon_name: property(APPLICATION_ICON_NAME),
712 sink: info.sink,
713 volumes: info.volume,
714 map: info.channel_map,
715 muted: info.mute,
716 corked: info.corked,
717 }
718 }
719}
720
721#[derive(Clone, PartialEq)]
723pub(crate) struct Info {
724 index: u32,
725 name: String,
726 volumes: ChannelVolumes,
727 muted: bool,
728}
729
730impl Pulse {
731 fn connect(&mut self, server: Option<&str>) -> Result<(), String> {
734 let lock = Locked::new(&mut self.mainloop);
735 let mut context = Context::new(&*lock.0, "crabshell").ok_or("libpulse made no context")?;
736 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
737 context.set_state_callback(Some(Box::new(on_pa_thread(move || {
738 let _ = sender.send((generation, Event::State));
739 }))));
740 let connected = context.connect(server, FlagSet::NOAUTOSPAWN, None);
742 self.context = Some(context);
743 connected.map_err(|error| format!("{error}"))
744 }
745
746 fn on_state(&mut self) -> Result<(), String> {
750 let lock = Locked::new(&mut self.mainloop);
751 let Some(context) = self.context.as_mut() else {
752 return Ok(());
753 };
754 match context.get_state() {
755 State::Ready if self.intro.is_none() => {
757 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
758 let on_event = move |facility, _, index| {
759 let event = match facility {
760 Some(Facility::Server) => Event::Server,
761 Some(Facility::Sink) => Event::Sink(index),
762 Some(Facility::Source) => Event::Source,
763 Some(Facility::SinkInput) => Event::Stream,
764 _ => return,
765 };
766 let _ = sender.send((generation, event));
767 };
768 context.set_subscribe_callback(Some(Box::new(on_pa_thread(on_event))));
769 let interest = InterestMaskSet::SINK
770 | InterestMaskSet::SOURCE
771 | InterestMaskSet::SINK_INPUT
772 | InterestMaskSet::SERVER;
773 context.subscribe(interest, on_pa_thread(|_| {}));
774 self.intro = Some(context.introspect());
775 drop(lock);
776 if std::mem::take(&mut self.down) {
777 tracing::info!("the audio server is back");
778 } else {
779 tracing::debug!("connected to the audio server");
780 }
781 self.read_default();
782 self.read_lists();
783 Ok(())
784 }
785 State::Failed | State::Terminated => Err(format!("{}", context.errno())),
786 _ => Ok(()),
787 }
788 }
789
790 fn when_ready(&mut self, call: impl FnOnce(&mut Introspector)) -> bool {
794 self.when_ready_with(|_, intro| call(intro))
795 }
796
797 fn when_ready_with(&mut self, call: impl FnOnce(&mut Context, &mut Introspector)) -> bool {
799 let _lock = Locked::new(&mut self.mainloop);
800 match (&mut self.context, &mut self.intro) {
801 (Some(context), Some(intro)) if context.get_state() == State::Ready => {
802 call(context, intro);
803 true
804 }
805 _ => false,
806 }
807 }
808
809 fn read_default(&mut self) {
811 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
812 self.when_ready(|intro| {
813 intro.get_server_info(on_pa_thread(move |info: &ServerInfo| {
816 let name = |name: &Option<std::borrow::Cow<'_, str>>| {
817 let name = name.as_deref().filter(|name| !name.is_empty());
818 name.map(str::to_owned)
819 };
820 let _ = sender.send((generation, Event::Default(name(&info.default_sink_name))));
821 let source = Event::DefaultSource(name(&info.default_source_name));
822 let _ = sender.send((generation, source));
823 }));
824 });
825 }
826
827 fn read_sink(&mut self, name: Option<String>) {
829 let Some(name) = name else {
830 return self.show(None);
831 };
832 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
833 self.when_ready(|intro| {
834 let mut found = None;
835 let on_result = move |result: ListResult<&SinkInfo>| match result {
836 ListResult::Item(info) => found = Info::read(info),
837 ListResult::End | ListResult::Error => {
838 let _ = sender.send((generation, Event::Info(found.take())));
839 }
840 };
841 intro.get_sink_info_by_name(&name, on_pa_thread(on_result));
842 });
843 }
844
845 fn read_lists(&mut self) {
847 self.read_sinks();
848 self.read_sources();
849 self.read_streams();
850 }
851
852 fn read_sinks(&mut self) {
853 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
854 self.when_ready(|intro| {
855 let mut found = Vec::new();
856 let on_result = move |result: ListResult<&SinkInfo>| match result {
857 ListResult::Item(info) => {
858 let read = node(
859 info.index,
860 &info.name,
861 &info.description,
862 info.volume,
863 info.channel_map,
864 info.mute,
865 );
866 let monitor_source = info.monitor_source_name.as_deref().map(str::to_owned);
867 let running = info.state == SinkState::Running;
868 found.extend(read.map(|node| NodeInfo {
869 monitor_source,
870 running,
871 ..node
872 }));
873 }
874 ListResult::End => {
875 let _ = sender.send((generation, Event::Sinks(std::mem::take(&mut found))));
876 }
877 ListResult::Error => tracing::warn!("the audio server didn't list its sinks"),
879 };
880 intro.get_sink_info_list(on_pa_thread(on_result));
881 });
882 }
883
884 fn read_sources(&mut self) {
885 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
886 self.when_ready(|intro| {
887 let mut found = Vec::new();
888 let on_result = move |result: ListResult<&SourceInfo>| match result {
889 ListResult::Item(info) => {
890 let read = node(
891 info.index,
892 &info.name,
893 &info.description,
894 info.volume,
895 info.channel_map,
896 info.mute,
897 );
898 let monitor = info.monitor_of_sink.is_some();
899 found.extend(read.map(|node| NodeInfo { monitor, ..node }));
900 }
901 ListResult::End => {
902 let _ = sender.send((generation, Event::Sources(std::mem::take(&mut found))));
903 }
904 ListResult::Error => tracing::warn!("the audio server didn't list its sources"),
905 };
906 intro.get_source_info_list(on_pa_thread(on_result));
907 });
908 }
909
910 fn read_streams(&mut self) {
911 let (sender, generation) = (Arc::clone(&self.sender), self.generation);
912 self.when_ready(|intro| {
913 let mut found = Vec::new();
914 let on_result = move |result: ListResult<&SinkInputInfo>| match result {
915 ListResult::Item(info) if info.volume.is_valid() => found.push(StreamInfo::read(info)),
916 ListResult::Item(_) => {}
917 ListResult::End => {
918 let _ = sender.send((generation, Event::Streams(std::mem::take(&mut found))));
919 }
920 ListResult::Error => tracing::warn!("the audio server didn't list its streams"),
921 };
922 intro.get_sink_input_info_list(on_pa_thread(on_result));
923 });
924 }
925
926 fn show_lists(&mut self) {
929 let Nodes {
930 sinks,
931 sources,
932 streams,
933 default_source,
934 } = &self.nodes;
935 let capped = |nodes: &[NodeInfo]| -> Vec<AudioNode> {
936 nodes.iter().take(MAX_NODES).map(NodeInfo::node).collect()
937 };
938 let sink_name = |index| {
939 let sink = sinks.iter().find(|sink| sink.index == index);
940 sink.map_or(String::new(), |sink| sink.name.clone())
941 };
942 let streams = streams
943 .iter()
944 .take(MAX_NODES)
945 .map(|stream| {
946 let (volumes, channels) = each_channel(&stream.volumes, &stream.map);
947 AudioStream {
948 index: stream.index,
949 app_name: stream.app_name.clone(),
950 title: stream.title.clone(),
951 icon_name: stream.icon_name.clone(),
952 sink: sink_name(stream.sink),
953 volume_percent: percent(stream.volumes.avg()),
954 volumes,
955 channels,
956 muted: stream.muted,
957 }
958 })
959 .collect();
960 let default = default_source
961 .as_ref()
962 .and_then(|name| sources.iter().find(|source| source.name == *name))
963 .map(NodeInfo::node);
964 set_changed(self.lists.sinks, capped(sinks));
965 let listed: Vec<NodeInfo> = sources.iter().filter(|s| !s.monitor).cloned().collect();
968 set_changed(self.lists.sources, capped(&listed));
969 set_changed(self.lists.streams, streams);
970 set_changed(self.lists.default_source, default);
971 }
972
973 fn set_default(&mut self, kind: Kind, name: &str) {
974 let sent = self.when_ready_with(|context, _| {
975 let done = on_pa_thread(|_| {});
976 match kind {
977 Kind::Sink => drop(context.set_default_sink(name, done)),
978 Kind::Source => drop(context.set_default_source(name, done)),
979 }
980 });
981 if !sent {
982 tracing::debug!(name, "no audio server: the default stays");
983 }
984 }
985
986 fn set_node_volume(&mut self, kind: Kind, name: &str, percent: u32) {
987 let nodes = match kind {
988 Kind::Sink => &self.nodes.sinks,
989 Kind::Source => &self.nodes.sources,
990 };
991 let Some(node) = nodes.iter().find(|node| node.name == name) else {
992 return tracing::debug!(name, ?kind, "no such node: its volume stays");
993 };
994 let volumes = at(node.volumes, percent);
995 self.when_ready(|intro| match kind {
996 Kind::Sink => drop(intro.set_sink_volume_by_name(name, &volumes, None)),
997 Kind::Source => drop(intro.set_source_volume_by_name(name, &volumes, None)),
998 });
999 }
1000
1001 fn set_node_volumes(&mut self, kind: Kind, name: &str, percents: &[u32]) {
1002 let nodes = match kind {
1003 Kind::Sink => &self.nodes.sinks,
1004 Kind::Source => &self.nodes.sources,
1005 };
1006 let Some(node) = nodes.iter().find(|node| node.name == name) else {
1007 return tracing::debug!(name, ?kind, "no such node: its volumes stay");
1008 };
1009 let Some(volumes) = each_at(node.volumes, percents) else {
1010 return tracing::warn!(
1011 name,
1012 ?kind,
1013 ?percents,
1014 "not one volume per channel: none set"
1015 );
1016 };
1017 self.when_ready(|intro| match kind {
1018 Kind::Sink => drop(intro.set_sink_volume_by_name(name, &volumes, None)),
1019 Kind::Source => drop(intro.set_source_volume_by_name(name, &volumes, None)),
1020 });
1021 }
1022
1023 fn set_node_mute(&mut self, kind: Kind, name: &str, muted: bool) {
1024 self.when_ready(|intro| match kind {
1025 Kind::Sink => drop(intro.set_sink_mute_by_name(name, muted, None)),
1026 Kind::Source => drop(intro.set_source_mute_by_name(name, muted, None)),
1027 });
1028 }
1029
1030 fn set_stream_volume(&mut self, index: u32, percent: u32) {
1031 let Some(stream) = self
1032 .nodes
1033 .streams
1034 .iter()
1035 .find(|stream| stream.index == index)
1036 else {
1037 return tracing::debug!(index, "no such stream: its volume stays");
1038 };
1039 let volumes = at(stream.volumes, percent);
1040 self.when_ready(|intro| drop(intro.set_sink_input_volume(index, &volumes, None)));
1041 }
1042
1043 fn set_stream_volumes(&mut self, index: u32, percents: &[u32]) {
1044 let streams = &self.nodes.streams;
1045 let Some(stream) = streams.iter().find(|stream| stream.index == index) else {
1046 return tracing::debug!(index, "no such stream: its volumes stay");
1047 };
1048 let Some(volumes) = each_at(stream.volumes, percents) else {
1049 return tracing::warn!(index, ?percents, "not one volume per channel: none set");
1050 };
1051 self.when_ready(|intro| drop(intro.set_sink_input_volume(index, &volumes, None)));
1052 }
1053
1054 fn set_stream_mute(&mut self, index: u32, muted: bool) {
1055 self.when_ready(|intro| drop(intro.set_sink_input_mute(index, muted, None)));
1056 }
1057
1058 fn move_stream(&mut self, index: u32, sink: &str) {
1059 self.when_ready(|intro| drop(intro.move_sink_input_by_name(index, sink, None)));
1060 }
1061
1062 fn show(&mut self, info: Option<Info>) {
1065 let stands = match (&self.asked, &info) {
1067 (Some((asked, _)), Some(read)) => read != asked && read.index == asked.index,
1068 _ => false,
1069 };
1070 if !stands {
1071 self.asked = None;
1072 }
1073 let sink = info.as_ref().map(Info::sink);
1074 self.shown = info;
1075 if self.sink.try_with(|shown| *shown != sink) == Some(true) {
1076 self.sink.set(sink);
1077 }
1078 }
1079
1080 fn base(&self) -> Option<Info> {
1083 match &self.asked {
1084 Some((asked, at)) if at.elapsed() < SETTLE => Some(asked.clone()),
1085 _ => self.shown.clone(),
1086 }
1087 }
1088
1089 fn change_volume(&mut self, percent: i32) {
1090 let Some(mut next) = self.base() else {
1091 return tracing::debug!("no default sink: the volume stays");
1092 };
1093 next.volumes = changed(next.volumes, percent);
1094 let sent = self.when_ready(|intro| {
1095 intro.set_sink_volume_by_name(&next.name, &next.volumes, None);
1096 });
1097 if sent {
1098 self.asked = Some((next, Instant::now()));
1099 } else {
1100 tracing::debug!("no audio server: the volume stays");
1101 }
1102 }
1103
1104 fn toggle_mute(&mut self) {
1105 let Some(mut next) = self.base() else {
1106 return tracing::debug!("no default sink: nothing to mute");
1107 };
1108 next.muted = !next.muted;
1109 let sent = self.when_ready(|intro| {
1110 intro.set_sink_mute_by_name(&next.name, next.muted, None);
1111 });
1112 if sent {
1113 self.asked = Some((next, Instant::now()));
1114 } else {
1115 tracing::debug!("no audio server: nothing to mute");
1116 }
1117 }
1118
1119 fn disconnect(&mut self) -> bool {
1122 let lock = Locked::new(&mut self.mainloop);
1123 peaks::close_all(&mut self.meters, &mut self.closing);
1124 self.intro = None;
1125 if let Some(mut context) = self.context.take() {
1126 context.set_state_callback(None);
1128 context.set_subscribe_callback(None);
1129 context.disconnect();
1130 }
1131 drop(lock);
1132 self.generation += 1;
1133 self.show(None);
1134 self.nodes = Nodes::default();
1135 self.show_lists();
1136 !std::mem::replace(&mut self.down, true)
1137 }
1138}
1139
1140impl Drop for Pulse {
1141 fn drop(&mut self) {
1142 self.mainloop.stop();
1145 }
1146}
1147
1148impl Info {
1149 fn read(info: &SinkInfo) -> Option<Info> {
1152 let (Some(name), true) = (info.name.as_deref(), info.volume.is_valid()) else {
1153 tracing::warn!(
1154 index = info.index,
1155 "skipped a sink with no name or a bad volume"
1156 );
1157 return None;
1158 };
1159 Some(Info {
1160 index: info.index,
1161 name: name.to_owned(),
1162 volumes: info.volume,
1163 muted: info.mute,
1164 })
1165 }
1166
1167 fn sink(&self) -> Sink {
1168 Sink {
1169 name: self.name.clone(),
1170 volume_percent: percent(self.volumes.avg()),
1171 muted: self.muted,
1172 }
1173 }
1174}
1175
1176struct Locked<'a>(&'a mut Mainloop);
1179
1180impl<'a> Locked<'a> {
1181 fn new(mainloop: &'a mut Mainloop) -> Locked<'a> {
1182 mainloop.lock();
1183 Locked(mainloop)
1184 }
1185}
1186
1187impl Drop for Locked<'_> {
1188 fn drop(&mut self) {
1189 self.0.unlock();
1190 }
1191}
1192
1193fn on_pa_thread<F: Send + 'static>(callback: F) -> F {
1196 callback
1197}
1198
1199fn percent(volume: Volume) -> u32 {
1202 let norm = u64::from(Volume::NORMAL.0);
1203 ((u64::from(volume.0) * 100 + norm / 2) / norm) as u32
1205}
1206
1207fn changed(mut volumes: ChannelVolumes, delta: i32) -> ChannelVolumes {
1212 let norm = i64::from(Volume::NORMAL.0);
1213 let average = volumes.avg();
1214 let target = (i64::from(percent(average)) + i64::from(delta)).clamp(0, 100);
1215 let shift = (target * norm + 50) / 100 - i64::from(average.0);
1216 for volume in volumes.get_mut() {
1217 volume.0 = (i64::from(volume.0) + shift).clamp(0, norm) as u32;
1219 }
1220 volumes
1221}
1222
1223fn node(
1226 index: u32,
1227 name: &Option<std::borrow::Cow<'_, str>>,
1228 description: &Option<std::borrow::Cow<'_, str>>,
1229 volumes: ChannelVolumes,
1230 map: Map,
1231 muted: bool,
1232) -> Option<NodeInfo> {
1233 let name = name.as_deref().filter(|_| volumes.is_valid())?;
1234 Some(NodeInfo {
1235 index,
1236 name: name.to_owned(),
1237 description: description.as_deref().unwrap_or(name).to_owned(),
1238 volumes,
1239 map,
1240 muted,
1241 monitor: false,
1242 monitor_source: None,
1243 running: false,
1244 })
1245}
1246
1247fn each_channel(volumes: &ChannelVolumes, map: &Map) -> (Vec<u32>, Vec<AudioChannel>) {
1250 let max = usize::from(Map::CHANNELS_MAX);
1251 let percents: Vec<u32> = volumes
1252 .get()
1253 .iter()
1254 .take(max)
1255 .map(|v| percent(*v))
1256 .collect();
1257 let positions = map.get().iter().map(|&position| channel(position));
1258 let positions = positions.chain(std::iter::repeat(AudioChannel::Unknown));
1259 let channels = positions.take(percents.len()).collect();
1260 (percents, channels)
1261}
1262
1263fn channel(position: Position) -> AudioChannel {
1265 use AudioChannel as C;
1266 let aux = position as i32 - Position::Aux0 as i32;
1268 match position {
1269 Position::Mono => C::Mono,
1270 Position::FrontLeft => C::FrontLeft,
1271 Position::FrontRight => C::FrontRight,
1272 Position::FrontCenter => C::FrontCenter,
1273 Position::RearCenter => C::RearCenter,
1274 Position::RearLeft => C::RearLeft,
1275 Position::RearRight => C::RearRight,
1276 Position::Lfe => C::LowFrequencyEffects,
1277 Position::FrontLeftOfCenter => C::FrontLeftCenter,
1278 Position::FrontRightOfCenter => C::FrontRightCenter,
1279 Position::SideLeft => C::SideLeft,
1280 Position::SideRight => C::SideRight,
1281 Position::TopCenter => C::TopCenter,
1282 Position::TopFrontLeft => C::TopFrontLeft,
1283 Position::TopFrontRight => C::TopFrontRight,
1284 Position::TopFrontCenter => C::TopFrontCenter,
1285 Position::TopRearLeft => C::TopRearLeft,
1286 Position::TopRearRight => C::TopRearRight,
1287 Position::TopRearCenter => C::TopRearCenter,
1288 _ => match u8::try_from(aux) {
1289 Ok(aux) if aux < 32 => C::Aux(aux),
1290 _ => C::Unknown,
1291 },
1292 }
1293}
1294
1295fn each_at(mut volumes: ChannelVolumes, percents: &[u32]) -> Option<ChannelVolumes> {
1298 if percents.len() != usize::from(volumes.len()) {
1299 return None;
1300 }
1301 let norm = Volume::NORMAL.0;
1302 for (volume, percent) in volumes.get_mut().iter_mut().zip(percents) {
1303 *volume = Volume(((*percent).min(100) * norm + 50) / 100);
1305 }
1306 Some(volumes)
1307}
1308
1309fn at(volumes: ChannelVolumes, percent: u32) -> ChannelVolumes {
1312 let shown = self::percent(volumes.avg());
1313 changed(volumes, percent.min(100) as i32 - shown.min(10_000) as i32)
1315}