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!("{} (x{})", destination, 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(format!("{}", info_by_type))
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(format!("{}", info_by_type))
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(format!("{}", info_by_type))
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(format!("{}", info_by_type))
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(format!("{}", info_by_type))
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}