i3status_rs/blocks/privacy/
pipewire.rs1use 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 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}