Skip to main content

crabshell_services/
audio.rs

1//! Audio: the default sink's volume and mute, and every sink, source and
2//! app stream with theirs, from the PulseAudio server, which
3//! pipewire-pulse serves on PipeWire systems.
4//!
5//! libpulse runs its mainloop on a thread of its own. Every libpulse call
6//! from this side runs on the main thread under the mainloop's lock.
7//! libpulse's callbacks run on its thread and only send a message over a
8//! calloop channel, so the signal is set on the main thread like any other.
9
10use 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
39/// How long the service waits before each reconnect.
40pub(crate) const RETRY: Duration = Duration::from_secs(2);
41
42/// How long the service keeps trying, from startup and from each drop of
43/// a working connection.
44pub(crate) const RETRY_FOR: Duration = Duration::from_secs(60);
45
46/// How long a command's result stands in for the server's reads.
47pub(crate) const SETTLE: Duration = Duration::from_millis(250);
48
49/// The most sinks, sources or streams kept, each.
50const MAX_NODES: usize = 128;
51
52/// Where the audio server is.
53pub(crate) struct Server {
54  /// In libpulse's server syntax. `None` for libpulse's default, which
55  /// `PULSE_SERVER` and client.conf can set.
56  pub(crate) address: Option<String>,
57  /// How long to wait before each reconnect: [`RETRY`], less in tests.
58  pub(crate) retry: Duration,
59  /// How long to keep trying: [`RETRY_FOR`], less in tests.
60  pub(crate) retry_for: Duration,
61}
62
63/// The default sink: where sound goes unless an app picks another.
64#[derive(Clone, Debug, PartialEq, Eq)]
65pub struct Sink {
66  /// Its name on the server, such as
67  /// `alsa_output.pci-0000_03_00.6.analog-stereo`.
68  pub name: String,
69  /// The average of its channels' volumes, rounded as `pactl` rounds
70  /// them. 100 is full volume, and other apps can set more.
71  pub volume_percent: u32,
72  /// Whether it is muted.
73  pub muted: bool,
74}
75
76/// A sink or a source: where sound goes out, such as speakers, or comes
77/// in, such as a microphone.
78#[derive(Clone, Debug, PartialEq, Eq)]
79pub struct AudioNode {
80  /// Its name on the server, which the setters take.
81  pub name: String,
82  /// What a person calls it, such as `Built-in Audio Analog Stereo`.
83  pub description: String,
84  /// The average of its channels' volumes, as [`Sink`]'s.
85  pub volume_percent: u32,
86  /// Each channel's volume, rounded as `volume_percent` is, in the order
87  /// of `channels`.
88  pub volumes: Vec<u32>,
89  /// Where each channel plays, such as [`AudioChannel::FrontLeft`]. As
90  /// long as `volumes`.
91  pub channels: Vec<AudioChannel>,
92  /// Whether it is muted.
93  pub muted: bool,
94}
95
96/// An app playing sound: one of the server's sink inputs.
97#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct AudioStream {
99  /// The server's number for it, which the setters take.
100  pub index: u32,
101  /// The app's name, such as `Firefox`.
102  pub app_name: String,
103  /// What it plays, such as a tab's title.
104  pub title: String,
105  /// The app's icon name, or `""`.
106  pub icon_name: String,
107  /// The name of the sink it plays to.
108  pub sink: String,
109  /// The average of its channels' volumes, as [`Sink`]'s.
110  pub volume_percent: u32,
111  /// Each channel's volume, as [`AudioNode::volumes`].
112  pub volumes: Vec<u32>,
113  /// Where each channel plays, as [`AudioNode::channels`].
114  pub channels: Vec<AudioChannel>,
115  /// Whether it is muted.
116  pub muted: bool,
117}
118
119/// Where a channel plays, as PulseAudio's channel map names it, with
120/// Quickshell's names for `PwAudioChannel`.
121#[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  /// An auxiliary channel, 0 to 31.
137  Aux(u8),
138  TopCenter,
139  TopFrontLeft,
140  TopFrontRight,
141  TopFrontCenter,
142  TopRearLeft,
143  TopRearRight,
144  TopRearCenter,
145  /// A position the server didn't give.
146  Unknown,
147}
148
149/// The audio service, from `cx.audio()`. Cloning it is cheap.
150#[derive(Clone)]
151pub struct Audio {
152  sink: ReadSignal<Option<Sink>>,
153  lists: ReadLists,
154  /// The asking context's, which owns its peak meters.
155  owner: Owner,
156  /// Empty and never set, for a meter that can't record.
157  no_peaks: ReadSignal<Vec<f32>>,
158  /// `None` on a context the shell didn't set up.
159  service: Option<Rc<AudioService>>,
160}
161
162impl Audio {
163  /// The default sink. `None` while there is no audio server or the
164  /// server has no sink. Every caller gets the same signal.
165  pub fn default_sink(&self) -> ReadSignal<Option<Sink>> {
166    self.sink
167  }
168
169  /// Changes the default sink's volume by `percent`, louder when
170  /// positive, and keeps it within 0-100%, so a step from above 100%
171  /// lands on 100%. Every channel moves by the same amount, keeping their
172  /// balance, except that one reaching 0% or 100% stops there. With no
173  /// sink or no server it does nothing.
174  pub fn change_volume(&self, percent: i32) {
175    self.command(|pulse| pulse.change_volume(percent));
176  }
177
178  /// Mutes the default sink, or unmutes it. With no sink or no server it
179  /// does nothing.
180  pub fn toggle_mute(&self) {
181    self.command(Pulse::toggle_mute);
182  }
183
184  /// Every sink, in the server's order. Empty while there is no server.
185  pub fn sinks(&self) -> ReadSignal<Vec<AudioNode>> {
186    self.lists.sinks
187  }
188
189  /// Every source except sinks' monitors, in the server's order.
190  pub fn sources(&self) -> ReadSignal<Vec<AudioNode>> {
191    self.lists.sources
192  }
193
194  /// The default source, where a call's microphone sound comes from.
195  pub fn default_source(&self) -> ReadSignal<Option<AudioNode>> {
196    self.lists.default_source
197  }
198
199  /// Every app playing sound, in the server's order.
200  pub fn streams(&self) -> ReadSignal<Vec<AudioStream>> {
201    self.lists.streams
202  }
203
204  /// Makes the sink called `name` the default.
205  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  /// Makes the source called `name` the default.
211  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  /// Sets every channel of the sink called `name` to `percent`, at most
217  /// 100.
218  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  /// Sets each channel of the sink called `name` to its own percent, at
224  /// most 100, in the order of [`AudioNode::channels`], as a balance
225  /// slider does. A list of another length than the sink's channels is
226  /// logged and not sent.
227  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  /// Mutes the sink called `name`, or unmutes it.
233  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  /// Sets every channel of the source called `name` to `percent`, at most
239  /// 100.
240  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  /// Sets each channel of the source called `name`, as
246  /// [`set_sink_volumes`](Self::set_sink_volumes) does a sink's.
247  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  /// Mutes the source called `name`, or unmutes it, as a microphone
253  /// button does.
254  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  /// Sets every channel of the stream numbered `index` to `percent`, at
260  /// most 100.
261  pub fn set_stream_volume(&self, index: u32, percent: u32) {
262    self.command(move |pulse| pulse.set_stream_volume(index, percent));
263  }
264
265  /// Sets each channel of the stream numbered `index`, as
266  /// [`set_sink_volumes`](Self::set_sink_volumes) does a sink's.
267  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  /// Mutes the stream numbered `index`, or unmutes it.
273  pub fn set_stream_mute(&self, index: u32, muted: bool) {
274    self.command(move |pulse| pulse.set_stream_mute(index, muted));
275  }
276
277  /// Moves the stream numbered `index` to play to the sink called `sink`.
278  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
295/// `cx.audio()`.
296pub(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/// The lists the service shows, as signals to set or read.
319#[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/// A sink or a source, for the setters both have.
345#[derive(Clone, Copy, Debug)]
346enum Kind {
347  Sink,
348  Source,
349}
350
351/// The audio service of one shell. It connects on first use.
352pub(crate) struct AudioService {
353  shared: Rc<Shared>,
354  /// The default sink, under the root owner. Made on first use.
355  sink: OnceCell<Signal<Option<Sink>>>,
356  /// The sinks, sources and streams, made with `sink`.
357  lists: OnceCell<ListSignals>,
358  /// libpulse, from first use on. `None` before that, and if libpulse
359  /// couldn't start its thread.
360  pulse: RefCell<Option<Pulse>>,
361  /// The peak meters configs hold, recording or not.
362  meters: RefCell<Vec<peaks::Wanted>>,
363  next_meter: Cell<u64>,
364  /// What a meter that can't record shows, made with `sink`.
365  no_peaks: OnceCell<Signal<Vec<f32>>>,
366}
367
368/// What libpulse's thread sends: the generation of the context it came
369/// from, and what happened.
370pub(crate) type Message = (u32, Event);
371
372pub(crate) enum Event {
373  /// The context's state changed.
374  State,
375  /// The server changed, maybe its default sink.
376  Server,
377  /// The sink with this index changed.
378  Sink(u32),
379  /// The server's default sink name.
380  Default(Option<String>),
381  /// The server's default source name.
382  DefaultSource(Option<String>),
383  /// A source changed.
384  Source,
385  /// A stream changed.
386  Stream,
387  /// Every sink, read.
388  Sinks(Vec<NodeInfo>),
389  /// Every source but monitors, read.
390  Sources(Vec<NodeInfo>),
391  /// Every stream, read.
392  Streams(Vec<StreamInfo>),
393  /// The sink asked for by name, or `None` for no such sink.
394  Info(Option<Info>),
395  /// The peak meter with this id has peaks to read.
396  Peaks(u64),
397  /// The peak meter with this id changed its state.
398  MeterState(u64),
399  /// A closing peak meter's stream changed its state.
400  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      // On the loop's next turn, so `cx.audio()` never waits on libpulse.
419      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  /// Starts libpulse's thread and the first connection. Neither waits
444  /// for the server.
445  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    // From here on, dropping `pulse` stops libpulse's thread.
470    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  /// Connects to the server, or tries again later.
495  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    // The root is gone, and the shell with it.
501    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    // A message from a context that is gone is stale.
512    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  /// Shows the lists as last read, and moves the peak meters to match.
561  fn show_lists(&self, pulse: &mut Pulse) {
562    pulse.show_lists();
563    pulse.sync_meters(&self.meters.borrow());
564  }
565
566  /// Drops the connection, shows no sink and tries again every `retry`
567  /// until `retry_for` has passed since startup, or since a working
568  /// connection dropped. Logs once per outage, not on every try, and
569  /// once on giving up.
570  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    // It was ready, so the server was there: give it a new while.
579    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      // No timer from here on, so audio never wakes the shell again.
590      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  /// Runs `f` from the event loop once, `delay` from now.
598  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
611/// One shell's libpulse objects and what they last told us. It stays on
612/// the main thread: `Mainloop` holds an `Rc`, so the compiler won't let it
613/// go to another.
614struct Pulse {
615  // Declared before `mainloop`, so they drop before it, the streams
616  // before their context.
617  meters: Vec<peaks::Meter>,
618  /// Streams of meters closed before the server made them, waiting to be.
619  closing: Vec<peaks::Recording>,
620  intro: Option<Introspector>,
621  context: Option<Context>,
622  mainloop: Mainloop,
623  /// Shared by every callback. Each `Sender` wakes the loop when it
624  /// drops, so a callback that holds its own would wake the shell when
625  /// libpulse drops it, with nothing to read.
626  sender: Arc<Sender<Message>>,
627  /// Bumped when a context goes, so the messages it left behind are
628  /// dropped.
629  generation: u32,
630  /// The default sink as last read.
631  shown: Option<Info>,
632  /// The default sink as the last command left it, and when that was
633  /// sent. pipewire-pulse answers a read sent right after a set with the
634  /// sink as it was before, so commands build on this instead until a
635  /// read echoes it or [`SETTLE`] passes.
636  asked: Option<(Info, Instant)>,
637  sink: Signal<Option<Sink>>,
638  lists: ListSignals,
639  /// Every sink, source and stream as last read.
640  nodes: Nodes,
641  /// Whether the server is gone and that was logged.
642  down: bool,
643  /// When the service stops trying to connect, if it can't.
644  retry_until: Instant,
645}
646
647/// Every sink, source and stream as libpulse last reported them.
648#[derive(Default)]
649struct Nodes {
650  sinks: Vec<NodeInfo>,
651  sources: Vec<NodeInfo>,
652  streams: Vec<StreamInfo>,
653  default_source: Option<String>,
654}
655
656/// A sink or source as libpulse reported it.
657#[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  /// A sink's monitor: a source, but not one listed.
666  monitor: bool,
667  /// The name of a sink's monitor, which a peak meter records.
668  monitor_source: Option<String>,
669  /// Whether a sink plays: an idle one sends its meters nothing.
670  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/// A sink input as libpulse reported it.
688#[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  /// Paused: it sends its meters nothing.
699  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/// The default sink as libpulse reported it.
722#[derive(Clone, PartialEq)]
723pub(crate) struct Info {
724  index: u32,
725  name: String,
726  volumes: ChannelVolumes,
727  muted: bool,
728}
729
730impl Pulse {
731  /// Makes a context and starts connecting it to `server`, or to
732  /// libpulse's default for `None`.
733  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    // NOAUTOSPAWN: a retry must never start a daemon next to pipewire-pulse.
741    let connected = context.connect(server, FlagSet::NOAUTOSPAWN, None);
742    self.context = Some(context);
743    connected.map_err(|error| format!("{error}"))
744  }
745
746  /// Acts on the context's new state. Once it is ready, subscribes to
747  /// sink and server events and reads the default sink. `Err` when the
748  /// connection failed or ended.
749  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      // Several queued messages can each find it ready.
756      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  /// Runs `call` under the lock if the context is ready, and says
791  /// whether it ran. libpulse returns no operation from a context that
792  /// isn't, and the binding panics on that.
793  fn when_ready(&mut self, call: impl FnOnce(&mut Introspector)) -> bool {
794    self.when_ready_with(|_, intro| call(intro))
795  }
796
797  /// [`when_ready`](Self::when_ready), with the context too.
798  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  /// Asks the server for its default sink's name.
810  fn read_default(&mut self) {
811    let (sender, generation) = (Arc::clone(&self.sender), self.generation);
812    self.when_ready(|intro| {
813      // shortcut: on an error reply libpulse-binding panics on its thread instead of calling
814      // this, and the panic hook prints it raw; logging it needs the C API, so `unsafe`.
815      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  /// Asks for the sink called `name`, or shows none.
828  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  /// Asks for every sink, source and stream.
846  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        // The last list stays.
878        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  /// Shows the lists as last read, capped. Each signal is set only when
927  /// what it shows changed.
928  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    // A sink's monitor isn't a source a person picks, though it can be
966    // the default, as while recording what plays.
967    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  /// Shows `info`, or no sink. The signal is set only when that changes
1063  /// what the bar shows, since every set repaints.
1064  fn show(&mut self, info: Option<Info>) {
1065    // An echo of the last command, or another sink, ends its stand-in.
1066    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  /// What the next command builds on: the last one's result while it
1081  /// stands in, or else the last read.
1082  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  /// Drops the context and shows no sink. `true` when this starts an
1120  /// outage.
1121  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      // Cleared first: disconnecting calls the state callback.
1127      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    // Unlocked, as libpulse asks. Once its thread is gone, the context
1143    // and introspector drop with nothing running beside them.
1144    self.mainloop.stop();
1145  }
1146}
1147
1148impl Info {
1149  /// Copies what we need out of libpulse's `info`, which lives only for
1150  /// the callback. A sink with no name or a bad volume is skipped.
1151  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
1176/// The mainloop's lock, held until this drops. libpulse's thread waits for
1177/// it, so it must be let go even on an early return or a panic.
1178struct 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
1193/// libpulse runs its callbacks on its own thread but doesn't ask for
1194/// `Send`. Wrapping each one in this makes the compiler check it.
1195fn on_pa_thread<F: Send + 'static>(callback: F) -> F {
1196  callback
1197}
1198
1199/// The percent `pactl get-sink-volume` shows for `volume`: 100 is
1200/// `PA_VOLUME_NORM`, and halves round up.
1201fn percent(volume: Volume) -> u32 {
1202  let norm = u64::from(Volume::NORMAL.0);
1203  // At most u32::MAX * 100 / norm, which fits.
1204  ((u64::from(volume.0) * 100 + norm / 2) / norm) as u32
1205}
1206
1207/// `volumes` changed by `delta` percent. Every channel moves by the amount
1208/// that puts their average on the shown percent plus `delta`, kept within
1209/// 0-100%. A channel that would pass 0% or 100% stops there, and then the
1210/// average falls short of that.
1211fn 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    // Within 0..=norm, which fits.
1218    volume.0 = (i64::from(volume.0) + shift).clamp(0, norm) as u32;
1219  }
1220  volumes
1221}
1222
1223/// A sink or source as libpulse reported it, unless it has no name or a
1224/// bad volume.
1225fn 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
1247/// Each channel's percent and position, at most `PA_CHANNELS_MAX` of
1248/// them. A position the map lacks is unknown, so both are as long.
1249fn 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
1263/// Quickshell's name for a PulseAudio channel position.
1264fn channel(position: Position) -> AudioChannel {
1265  use AudioChannel as C;
1266  // Aux0 to Aux31 are numbered in a row.
1267  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
1295/// `volumes` with each channel at its own percent, at most 100, or `None`
1296/// when `percents` doesn't have one per channel.
1297fn 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    // Rounded as pactl sets a percent; at most 100 * norm + 50, which fits.
1304    *volume = Volume(((*percent).min(100) * norm + 50) / 100);
1305  }
1306  Some(volumes)
1307}
1308
1309/// `volumes` moved so their average is `percent`, at most 100, keeping
1310/// the channels' balance as [`changed`] does.
1311fn at(volumes: ChannelVolumes, percent: u32) -> ChannelVolumes {
1312  let shown = self::percent(volumes.avg());
1313  // Both at most a few thousand, which fit.
1314  changed(volumes, percent.min(100) as i32 - shown.min(10_000) as i32)
1315}