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