i3status_rs/blocks/
privacy.rs1use futures::future::{select_all, try_join_all};
72
73use super::prelude::*;
74
75make_log_macro!(debug, "privacy");
76
77#[cfg(feature = "pipewire")]
78mod pipewire;
79mod v4l;
80
81#[derive(Deserialize, Debug)]
82#[serde(deny_unknown_fields)]
83pub struct Config {
84    #[serde(default)]
85    pub format: FormatConfig,
86    #[serde(default)]
87    pub format_alt: FormatConfig,
88    pub driver: Vec<PrivacyDriver>,
89}
90
91#[derive(Deserialize, Debug)]
92#[serde(tag = "name", rename_all = "snake_case")]
93pub enum PrivacyDriver {
94    #[cfg(feature = "pipewire")]
95    Pipewire(pipewire::Config),
96    V4l(v4l::Config),
97}
98
99#[derive(Debug, Clone, Eq, Hash, PartialEq)]
100enum Type {
101    Audio,
102    AudioSink,
103    Video,
104    Webcam,
105    Unknown,
106}
107
108type PrivacyInfo = HashMap<Type, PrivacyInfoInner>;
110
111type PrivacyInfoInnerType = HashMap<String, HashMap<String, usize>>;
112#[derive(Default, Debug)]
113struct PrivacyInfoInner(PrivacyInfoInnerType);
114
115impl std::ops::Deref for PrivacyInfoInner {
116    type Target = PrivacyInfoInnerType;
117    fn deref(&self) -> &Self::Target {
118        &self.0
119    }
120}
121
122impl std::ops::DerefMut for PrivacyInfoInner {
123    fn deref_mut(&mut self) -> &mut Self::Target {
124        &mut self.0
125    }
126}
127
128impl std::fmt::Display for PrivacyInfoInner {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        write!(
131            f,
132            "{{ {} }}",
133            itertools::join(
134                self.iter().map(|(source, destinations)| {
135                    format!(
136                        "{} => [ {} ]",
137                        source,
138                        itertools::join(
139                            destinations
140                                .iter()
141                                .map(|(destination, count)| if count == &1 {
142                                    destination.into()
143                                } else {
144                                    format!("{destination} (x{count})")
145                                }),
146                            ", "
147                        )
148                    )
149                }),
150                ", ",
151            )
152        )
153    }
154}
155
156#[async_trait]
157trait PrivacyMonitor {
158    async fn get_info(&mut self) -> Result<PrivacyInfo>;
159    async fn wait_for_change(&mut self) -> Result<()>;
160}
161
162pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
163    let mut actions = api.get_actions()?;
164    api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
165
166    let mut format = config.format.with_default(
167        "{ $icon_audio |}{ $icon_audio_sink |}{ $icon_video |}{ $icon_webcam |}{ $icon_unknown |}",
168    )?;
169    let mut format_alt = config.format_alt.with_default("{ $icon_audio $info_audio |}{ $icon_audio_sink $info_audio_sink |}{ $icon_video $info_video |}{ $icon_webcam $info_webcam |}{ $icon_unknown $info_unknown |}")?;
170
171    let mut drivers: Vec<Box<dyn PrivacyMonitor + Send + Sync>> = Vec::new();
172
173    for driver in &config.driver {
174        drivers.push(match driver {
175            #[cfg(feature = "pipewire")]
176            PrivacyDriver::Pipewire(driver_config) => {
177                Box::new(pipewire::Monitor::new(driver_config).await?)
178            }
179            PrivacyDriver::V4l(driver_config) => {
180                Box::new(v4l::Monitor::new(driver_config, api.error_interval).await?)
181            }
182        });
183    }
184
185    loop {
186        let mut widget = Widget::new().with_format(format.clone());
187
188        let mut info = PrivacyInfo::default();
189        for driver_info in try_join_all(drivers.iter_mut().map(|driver| driver.get_info())).await? {
191            for (type_, mapping) in driver_info {
192                let existing_mapping = info.entry(type_).or_default();
193                for (source, dest) in mapping.0 {
194                    existing_mapping.entry(source).or_default().extend(dest);
195                }
196            }
197        }
198        if !info.is_empty() {
199            widget.state = State::Warning;
200        }
201
202        let mut values = Values::new();
203
204        if let Some(info_by_type) = info.get(&Type::Audio) {
205            map! { @extend values
206                "icon_audio" => Value::icon("microphone"),
207                "info_audio" => Value::text(info_by_type.to_string())
208            }
209        }
210        if let Some(info_by_type) = info.get(&Type::AudioSink) {
211            map! { @extend values
212                "icon_audio_sink" => Value::icon("volume"),
213                "info_audio_sink" => Value::text(info_by_type.to_string())
214            }
215        }
216        if let Some(info_by_type) = info.get(&Type::Video) {
217            map! { @extend values
218                "icon_video" => Value::icon("xrandr"),
219                "info_video" => Value::text(info_by_type.to_string())
220            }
221        }
222        if let Some(info_by_type) = info.get(&Type::Webcam) {
223            map! { @extend values
224                "icon_webcam" => Value::icon("webcam"),
225                "info_webcam" => Value::text(info_by_type.to_string())
226            }
227        }
228        if let Some(info_by_type) = info.get(&Type::Unknown) {
229            map! { @extend values
230                "icon_unknown" => Value::icon("unknown"),
231                "info_unknown" => Value::text(info_by_type.to_string())
232            }
233        }
234
235        widget.set_values(values);
236
237        api.set_widget(widget)?;
238
239        select! {
240            _ = api.wait_for_update_request() => (),
241            _ = select_all(drivers.iter_mut().map(|driver| driver.wait_for_change())) =>(),
242            Some(action) = actions.recv() => match action.as_ref() {
243                "toggle_format" => {
244                    std::mem::swap(&mut format_alt, &mut format);
245                }
246                _ => (),
247            }
248        }
249    }
250}