i3status_rs/blocks/sound/
pipewire.rs

1use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
2
3use super::*;
4use crate::pipewire::{CLIENT, CommandKind, EventKind, PIPEWIRE_CONNECTION_ERROR_MSG, PwSender};
5
6pub(super) struct Device {
7    device_kind: DeviceKind,
8    match_name: Option<String>,
9    id: Option<u32>,
10    name: String,
11    description: Option<String>,
12    active_port: Option<String>,
13    form_factor: Option<String>,
14    volume: Vec<f32>,
15    volume_avg: u32,
16    muted: bool,
17    updates: UnboundedReceiver<EventKind>,
18    command_sender: PwSender<CommandKind>,
19}
20
21impl Device {
22    pub(super) async fn new(device_kind: DeviceKind, match_name: Option<String>) -> Result<Self> {
23        let client = CLIENT.as_ref().error("Could not get client")?;
24
25        let (tx, rx) = unbounded_channel();
26        client.add_event_listener(tx);
27        let command_sender = client.add_command_listener();
28        let mut s = Self {
29            device_kind,
30            match_name,
31            id: None,
32            name: "".into(),
33            description: None,
34            active_port: None,
35            form_factor: None,
36            volume: Vec::new(),
37            volume_avg: 0,
38            muted: false,
39            updates: rx,
40            command_sender,
41        };
42        s.get_info().await?;
43        Ok(s)
44    }
45}
46
47#[async_trait]
48impl SoundDevice for Device {
49    fn volume(&self) -> u32 {
50        self.volume_avg
51    }
52
53    fn muted(&self) -> bool {
54        self.muted
55    }
56
57    fn output_name(&self) -> String {
58        self.name.clone()
59    }
60
61    fn output_description(&self) -> Option<String> {
62        self.description.clone()
63    }
64
65    fn active_port(&self) -> Option<String> {
66        self.active_port.clone()
67    }
68
69    fn form_factor(&self) -> Option<&str> {
70        self.form_factor.as_deref()
71    }
72
73    async fn get_info(&mut self) -> Result<()> {
74        let client = CLIENT.as_ref().error("Could not get client")?;
75        if client.is_terminated() {
76            return Err(Error::new(PIPEWIRE_CONNECTION_ERROR_MSG));
77        }
78
79        let data = client.data.lock().unwrap();
80
81        let name = if self.match_name.is_some() {
82            // If name is specified in the config, then match that node
83            &self.match_name
84        } else {
85            // Otherwise use the default metadata to determine the node name
86            match self.device_kind {
87                DeviceKind::Sink => &data.default_metadata.sink,
88                DeviceKind::Source => &data.default_metadata.source,
89            }
90        };
91
92        let Some(name) = name else {
93            //Metadata may not be ready yet
94            return Ok(());
95        };
96
97        if let Some((id, node)) = data.nodes.iter().find(|(_, node)| node.name == *name) {
98            self.id = Some(*id);
99            if let Some(volume) = &node.volume {
100                self.volume = volume.clone();
101                self.volume_avg = (volume.iter().sum::<f32>() / volume.len() as f32).round() as u32;
102            }
103            if let Some(muted) = node.muted {
104                self.muted = muted;
105            }
106            self.name = node.name.clone();
107            self.description = node.description.clone();
108            self.form_factor = node.form_factor.clone();
109
110            if let Some(device_id) = node.device_id
111                && let Some(direction) = node.direction
112                && let Some(directed_routes) = data.directed_routes.get(&device_id)
113                && let Some(route) = directed_routes.get_route(direction)
114            {
115                self.active_port = Some(route.name.clone());
116            }
117        } else {
118            self.id = None;
119        }
120
121        Ok(())
122    }
123
124    async fn set_volume(&mut self, step: i32, max_vol: Option<u32>) -> Result<()> {
125        if let Some(id) = self.id {
126            let volume = self
127                .volume
128                .iter()
129                .map(|&vol| {
130                    let uncapped_vol = 0_f32.max(vol + step as f32);
131                    if let Some(vol_cap) = max_vol {
132                        uncapped_vol.min(vol_cap as f32)
133                    } else {
134                        uncapped_vol
135                    }
136                })
137                .collect();
138
139            self.command_sender
140                .send(CommandKind::SetVolume(id, volume))
141                .map_err(|_| Error::new("Could not set volume"))?;
142        }
143        Ok(())
144    }
145
146    async fn toggle(&mut self) -> Result<()> {
147        if let Some(id) = self.id {
148            self.command_sender
149                .send(CommandKind::Mute(id, !self.muted))
150                .map_err(|_| Error::new("Could not toggle mute"))?;
151        }
152        Ok(())
153    }
154
155    async fn wait_for_update(&mut self) -> Result<()> {
156        while let Some(event) = self.updates.recv().await {
157            if event.intersects(
158                EventKind::DEFAULT_META_DATA_UPDATED
159                    | EventKind::DEVICE_ADDED
160                    | EventKind::DEVICE_PARAM_UPDATE
161                    | EventKind::DEVICE_REMOVED
162                    | EventKind::NODE_PARAM_UPDATE
163                    | EventKind::NODE_STATE_UPDATE
164                    | EventKind::PORT_ADDED
165                    | EventKind::PORT_REMOVED,
166            ) {
167                break;
168            } else if event.contains(EventKind::PIPEWIRE_CONNECTION_ERROR) {
169                return Err(Error::new(PIPEWIRE_CONNECTION_ERROR_MSG));
170            }
171        }
172        Ok(())
173    }
174}