Skip to main content

i3status_rs/blocks/privacy/
pipewire.rs

1use itertools::Itertools as _;
2use tokio::sync::mpsc::{UnboundedReceiver, unbounded_channel};
3
4use super::*;
5use crate::pipewire::{CLIENT, EventKind, Link, Node, PIPEWIRE_CONNECTION_ERROR_MSG};
6
7#[derive(Deserialize, Debug, SmartDefault)]
8#[serde(rename_all = "lowercase", deny_unknown_fields, default)]
9pub struct Config {
10    exclude_output: Vec<String>,
11    exclude_input: Vec<String>,
12    display: NodeDisplay,
13}
14
15#[derive(Deserialize, Debug, SmartDefault)]
16#[serde(rename_all = "snake_case")]
17enum NodeDisplay {
18    #[default]
19    Name,
20    Description,
21    Nickname,
22}
23
24impl NodeDisplay {
25    fn map_node(&self, node: &Node) -> String {
26        match self {
27            NodeDisplay::Name => node.name.clone(),
28            NodeDisplay::Description => node.description.clone().unwrap_or(node.name.clone()),
29            NodeDisplay::Nickname => node.nick.clone().unwrap_or(node.name.clone()),
30        }
31    }
32}
33
34pub(super) struct Monitor<'a> {
35    config: &'a Config,
36    updates: UnboundedReceiver<EventKind>,
37}
38
39impl<'a> Monitor<'a> {
40    pub(super) async fn new(config: &'a Config) -> Result<Self> {
41        let client = CLIENT.as_ref().error("Could not get client")?;
42        let (tx, rx) = unbounded_channel();
43        client.add_event_listener(tx);
44        Ok(Self {
45            config,
46            updates: rx,
47        })
48    }
49}
50
51#[async_trait]
52impl PrivacyMonitor for Monitor<'_> {
53    async fn get_info(&mut self) -> Result<PrivacyInfo> {
54        let client = CLIENT.as_ref().error("Could not get client")?;
55        if client.is_terminated() {
56            return Err(Error::new(PIPEWIRE_CONNECTION_ERROR_MSG));
57        }
58        let data = client.data.lock().unwrap();
59        let mut mapping: PrivacyInfo = PrivacyInfo::new();
60
61        for node in data.nodes.values() {
62            debug! {"{:?}", node};
63        }
64
65        // The links must be sorted and then dedup'ed since you can multiple links between any given pair of nodes
66        for Link {
67            link_output_node,
68            link_input_node,
69            ..
70        } in data.links.values().sorted().dedup()
71        {
72            if let Some(output_node) = data.nodes.get(link_output_node)
73                && let Some(input_node) = data.nodes.get(link_input_node)
74                && input_node.media_class != Some("Audio/Sink".into())
75                && !self.config.exclude_output.contains(&output_node.name)
76                && !self.config.exclude_input.contains(&input_node.name)
77            {
78                let type_ = if input_node.media_class == Some("Stream/Input/Video".into()) {
79                    if output_node.media_role == Some("Camera".into()) {
80                        Type::Webcam
81                    } else {
82                        Type::Video
83                    }
84                } else if input_node.media_class == Some("Stream/Input/Audio".into()) {
85                    if output_node.media_class == Some("Audio/Sink".into()) {
86                        Type::AudioSink
87                    } else {
88                        Type::Audio
89                    }
90                } else {
91                    Type::Unknown
92                };
93                *mapping
94                    .entry(type_)
95                    .or_default()
96                    .entry(self.config.display.map_node(output_node))
97                    .or_default()
98                    .entry(self.config.display.map_node(input_node))
99                    .or_default() += 1;
100            }
101        }
102
103        Ok(mapping)
104    }
105
106    async fn wait_for_change(&mut self) -> Result<()> {
107        while let Some(event) = self.updates.recv().await {
108            if event.intersects(
109                EventKind::NODE_ADDED
110                    | EventKind::NODE_REMOVED
111                    | EventKind::LINK_ADDED
112                    | EventKind::LINK_REMOVED,
113            ) {
114                break;
115            } else if event.contains(EventKind::PIPEWIRE_CONNECTION_ERROR) {
116                return Err(Error::new(PIPEWIRE_CONNECTION_ERROR_MSG));
117            }
118        }
119        Ok(())
120    }
121}