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};
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 let data = client.data.lock().unwrap();
56 let mut mapping: PrivacyInfo = PrivacyInfo::new();
57
58 for node in data.nodes.values() {
59 debug! {"{:?}", node};
60 }
61
62 for Link {
64 link_output_node,
65 link_input_node,
66 ..
67 } in data.links.values().sorted().dedup()
68 {
69 if let Some(output_node) = data.nodes.get(link_output_node)
70 && let Some(input_node) = data.nodes.get(link_input_node)
71 && input_node.media_class != Some("Audio/Sink".into())
72 && !self.config.exclude_output.contains(&output_node.name)
73 && !self.config.exclude_input.contains(&input_node.name)
74 {
75 let type_ = if input_node.media_class == Some("Stream/Input/Video".into()) {
76 if output_node.media_role == Some("Camera".into()) {
77 Type::Webcam
78 } else {
79 Type::Video
80 }
81 } else if input_node.media_class == Some("Stream/Input/Audio".into()) {
82 if output_node.media_class == Some("Audio/Sink".into()) {
83 Type::AudioSink
84 } else {
85 Type::Audio
86 }
87 } else {
88 Type::Unknown
89 };
90 *mapping
91 .entry(type_)
92 .or_default()
93 .entry(self.config.display.map_node(output_node))
94 .or_default()
95 .entry(self.config.display.map_node(input_node))
96 .or_default() += 1;
97 }
98 }
99
100 Ok(mapping)
101 }
102
103 async fn wait_for_change(&mut self) -> Result<()> {
104 while let Some(event) = self.updates.recv().await {
105 if event.intersects(
106 EventKind::NODE_ADDED
107 | EventKind::NODE_REMOVED
108 | EventKind::LINK_ADDED
109 | EventKind::LINK_REMOVED,
110 ) {
111 break;
112 }
113 }
114 Ok(())
115 }
116}