Skip to content

Commit dcc010e

Browse files
committed
Improve output in privacy block
1 parent ea473c3 commit dcc010e

File tree

6 files changed

+102
-72
lines changed

6 files changed

+102
-72
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ icu_datetime = { version = "1.3.0", optional = true }
4646
icu_calendar = { version = "1.3.0", optional = true }
4747
icu_locid = { version = "1.3.0", optional = true }
4848
inotify = "0.10"
49+
itertools = "0.12"
4950
libc = "0.2"
5051
libpulse-binding = { version = "2.0", default-features = false, optional = true }
5152
log = "0.4"

cspell.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ words:
7474
- inotify
7575
- iowait
7676
- ipapi
77+
- itertools
7778
- iwctl
7879
- kbdd
7980
- kbddbus

src/blocks/privacy.rs

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@
6868
//! - `webcam`
6969
//! - `unknown`
7070
71-
use std::collections::HashSet;
72-
7371
use futures::future::{select_all, try_join_all};
7472

7573
use super::prelude::*;
@@ -114,8 +112,53 @@ enum Type {
114112
Unknown,
115113
}
116114

117-
// {type: {name: {reader}}
118-
type PrivacyInfo = HashMap<Type, HashMap<String, HashSet<String>>>;
115+
// {type: {source: {destination: count}}
116+
type PrivacyInfo = HashMap<Type, PrivacyInfoInner>;
117+
118+
type PrivacyInfoInnerType = HashMap<String, HashMap<String, usize>>;
119+
#[derive(Default, Debug)]
120+
struct PrivacyInfoInner(PrivacyInfoInnerType);
121+
122+
impl std::ops::Deref for PrivacyInfoInner {
123+
type Target = PrivacyInfoInnerType;
124+
fn deref(&self) -> &Self::Target {
125+
&self.0
126+
}
127+
}
128+
129+
impl std::ops::DerefMut for PrivacyInfoInner {
130+
fn deref_mut(&mut self) -> &mut Self::Target {
131+
&mut self.0
132+
}
133+
}
134+
135+
impl std::fmt::Display for PrivacyInfoInner {
136+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137+
write!(
138+
f,
139+
"{{ {} }}",
140+
itertools::join(
141+
self.iter().map(|(source, destinations)| {
142+
format!(
143+
"{} => [ {} ]",
144+
source,
145+
itertools::join(
146+
destinations
147+
.iter()
148+
.map(|(destination, count)| if count == &1 {
149+
destination.into()
150+
} else {
151+
format!("{} (x{})", destination, count)
152+
}),
153+
", "
154+
)
155+
)
156+
}),
157+
", ",
158+
)
159+
)
160+
}
161+
}
119162

120163
#[async_trait]
121164
trait PrivacyMonitor {
@@ -149,12 +192,12 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
149192
loop {
150193
let mut widget = Widget::new().with_format(format.clone());
151194

152-
let mut info = PrivacyInfo::new();
195+
let mut info = PrivacyInfo::default();
153196
//Merge driver info
154197
for driver_info in try_join_all(drivers.iter_mut().map(|driver| driver.get_info())).await? {
155198
for (type_, mapping) in driver_info {
156199
let existing_mapping = info.entry(type_).or_default();
157-
for (source, dest) in mapping {
200+
for (source, dest) in mapping.0 {
158201
existing_mapping.entry(source).or_default().extend(dest);
159202
}
160203
}
@@ -168,31 +211,31 @@ pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
168211
if let Some(info_by_type) = info.get(&Type::Audio) {
169212
values.extend(map! {
170213
"icon_audio" => Value::icon("microphone"),
171-
"info_audio" => Value::text(format!("{:?}", info_by_type))
214+
"info_audio" => Value::text(format!("{}", info_by_type))
172215
});
173216
}
174217
if let Some(info_by_type) = info.get(&Type::AudioSink) {
175218
values.extend(map! {
176219
"icon_audio_sink" => Value::icon("volume"),
177-
"info_audio_sink" => Value::text(format!("{:?}", info_by_type))
220+
"info_audio_sink" => Value::text(format!("{}", info_by_type))
178221
});
179222
}
180223
if let Some(info_by_type) = info.get(&Type::Video) {
181224
values.extend(map! {
182225
"icon_video" => Value::icon("xrandr"),
183-
"info_video" => Value::text(format!("{:?}", info_by_type))
226+
"info_video" => Value::text(format!("{}", info_by_type))
184227
});
185228
}
186229
if let Some(info_by_type) = info.get(&Type::Webcam) {
187230
values.extend(map! {
188231
"icon_webcam" => Value::icon("webcam"),
189-
"info_webcam" => Value::text(format!("{:?}", info_by_type))
232+
"info_webcam" => Value::text(format!("{}", info_by_type))
190233
});
191234
}
192235
if let Some(info_by_type) = info.get(&Type::Unknown) {
193236
values.extend(map! {
194237
"icon_unknown" => Value::icon("unknown"),
195-
"info_unknown" => Value::text(format!("{:?}", info_by_type))
238+
"info_unknown" => Value::text(format!("{}", info_by_type))
196239
});
197240
}
198241

src/blocks/privacy/pipewire.rs

Lines changed: 42 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use ::pipewire::{
22
context::Context, core::PW_ID_CORE, keys, main_loop::MainLoop, properties::properties,
33
spa::utils::dict::DictRef, types::ObjectType,
44
};
5+
use itertools::Itertools;
56
use tokio::sync::mpsc::{self, Receiver, Sender};
67

78
use std::{collections::HashMap, sync::Mutex, thread};
@@ -35,7 +36,7 @@ impl Node {
3536
}
3637
}
3738

38-
#[derive(Debug)]
39+
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord)]
3940
struct Link {
4041
link_output_node: u32,
4142
link_input_node: u32,
@@ -185,6 +186,16 @@ enum NodeDisplay {
185186
Nickname,
186187
}
187188

189+
impl NodeDisplay {
190+
fn map_node(&self, node: &Node) -> String {
191+
match self {
192+
NodeDisplay::Name => node.name.clone(),
193+
NodeDisplay::Description => node.description.clone().unwrap_or(node.name.clone()),
194+
NodeDisplay::Nickname => node.nick.clone().unwrap_or(node.name.clone()),
195+
}
196+
}
197+
}
198+
188199
pub(super) struct Monitor<'a> {
189200
config: &'a Config,
190201
updates: Receiver<()>,
@@ -215,73 +226,45 @@ impl<'a> PrivacyMonitor for Monitor<'a> {
215226
debug! {"{:?}", node};
216227
}
217228

229+
// The links must be sorted and then dedup'ed since you can multiple links between any given pair of nodes
218230
for Link {
219231
link_output_node,
220232
link_input_node,
221233
..
222-
} in data.links.values()
234+
} in data.links.values().sorted().dedup()
223235
{
224-
if let (Some(output_node), Some(input_node)) = (
236+
let (Some(output_node), Some(input_node)) = (
225237
data.nodes.get(link_output_node),
226238
data.nodes.get(link_input_node),
227-
) {
228-
if input_node.media_class != Some("Audio/Sink".into())
229-
&& !self.config.exclude_output.contains(&output_node.name)
230-
&& !self.config.exclude_input.contains(&input_node.name)
231-
{
232-
let type_ = if input_node.media_class == Some("Stream/Input/Video".into()) {
233-
if output_node.media_role == Some("Camera".into()) {
234-
Type::Webcam
235-
} else {
236-
Type::Video
237-
}
238-
} else if input_node.media_class == Some("Stream/Input/Audio".into()) {
239-
if output_node.media_class == Some("Audio/Sink".into()) {
240-
Type::AudioSink
241-
} else {
242-
Type::Audio
243-
}
239+
) else {
240+
continue;
241+
};
242+
if input_node.media_class != Some("Audio/Sink".into())
243+
&& !self.config.exclude_output.contains(&output_node.name)
244+
&& !self.config.exclude_input.contains(&input_node.name)
245+
{
246+
let type_ = if input_node.media_class == Some("Stream/Input/Video".into()) {
247+
if output_node.media_role == Some("Camera".into()) {
248+
Type::Webcam
244249
} else {
245-
Type::Unknown
246-
};
247-
use NodeDisplay::*;
248-
match self.config.display {
249-
Name => {
250-
mapping
251-
.entry(type_)
252-
.or_default()
253-
.entry(output_node.name.clone())
254-
.or_default()
255-
.insert(input_node.name.clone());
256-
}
257-
Description => {
258-
mapping
259-
.entry(type_)
260-
.or_default()
261-
.entry(
262-
output_node
263-
.description
264-
.clone()
265-
.unwrap_or(output_node.name.clone()),
266-
)
267-
.or_default()
268-
.insert(
269-
input_node
270-
.description
271-
.clone()
272-
.unwrap_or(input_node.name.clone()),
273-
);
274-
}
275-
Nickname => {
276-
mapping
277-
.entry(type_)
278-
.or_default()
279-
.entry(output_node.nick.clone().unwrap_or(output_node.name.clone()))
280-
.or_default()
281-
.insert(input_node.nick.clone().unwrap_or(input_node.name.clone()));
282-
}
250+
Type::Video
283251
}
284-
}
252+
} else if input_node.media_class == Some("Stream/Input/Audio".into()) {
253+
if output_node.media_class == Some("Audio/Sink".into()) {
254+
Type::AudioSink
255+
} else {
256+
Type::Audio
257+
}
258+
} else {
259+
Type::Unknown
260+
};
261+
*mapping
262+
.entry(type_)
263+
.or_default()
264+
.entry(self.config.display.map_node(output_node))
265+
.or_default()
266+
.entry(self.config.display.map_node(input_node))
267+
.or_default() += 1;
285268
}
286269
}
287270

src/blocks/privacy/v4l.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,12 +128,13 @@ impl<'a> PrivacyMonitor for Monitor<'a> {
128128
continue;
129129
}
130130
debug!("{} {:?}", reader, link_path);
131-
mapping
131+
*mapping
132132
.entry(Type::Webcam)
133133
.or_default()
134134
.entry(link_path.to_string_lossy().to_string())
135135
.or_default()
136-
.insert(reader);
136+
.entry(reader)
137+
.or_default() += 1;
137138
debug!("{:?}", mapping);
138139
}
139140
}

0 commit comments

Comments
 (0)