use futures::future::{select_all, try_join_all};
use super::prelude::*;
make_log_macro!(debug, "privacy");
#[cfg(feature = "pipewire")]
mod pipewire;
mod v4l;
#[derive(Deserialize, Debug)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default)]
pub format: FormatConfig,
#[serde(default)]
pub format_alt: FormatConfig,
pub driver: Vec<PrivacyDriver>,
}
#[derive(Deserialize, Debug)]
#[serde(tag = "name", rename_all = "snake_case")]
pub enum PrivacyDriver {
#[cfg(feature = "pipewire")]
Pipewire(pipewire::Config),
V4l(v4l::Config),
}
#[derive(Debug, Clone, Eq, Hash, PartialEq)]
enum Type {
Audio,
AudioSink,
Video,
Webcam,
Unknown,
}
type PrivacyInfo = HashMap<Type, PrivacyInfoInner>;
type PrivacyInfoInnerType = HashMap<String, HashMap<String, usize>>;
#[derive(Default, Debug)]
struct PrivacyInfoInner(PrivacyInfoInnerType);
impl std::ops::Deref for PrivacyInfoInner {
type Target = PrivacyInfoInnerType;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for PrivacyInfoInner {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl std::fmt::Display for PrivacyInfoInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{ {} }}",
itertools::join(
self.iter().map(|(source, destinations)| {
format!(
"{} => [ {} ]",
source,
itertools::join(
destinations
.iter()
.map(|(destination, count)| if count == &1 {
destination.into()
} else {
format!("{destination} (x{count})")
}),
", "
)
)
}),
", ",
)
)
}
}
#[async_trait]
trait PrivacyMonitor {
async fn get_info(&mut self) -> Result<PrivacyInfo>;
async fn wait_for_change(&mut self) -> Result<()>;
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let mut actions = api.get_actions()?;
api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
let mut format = config.format.with_default(
"{ $icon_audio |}{ $icon_audio_sink |}{ $icon_video |}{ $icon_webcam |}{ $icon_unknown |}",
)?;
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 |}")?;
let mut drivers: Vec<Box<dyn PrivacyMonitor + Send + Sync>> = Vec::new();
for driver in &config.driver {
drivers.push(match driver {
#[cfg(feature = "pipewire")]
PrivacyDriver::Pipewire(driver_config) => {
Box::new(pipewire::Monitor::new(driver_config).await?)
}
PrivacyDriver::V4l(driver_config) => {
Box::new(v4l::Monitor::new(driver_config, api.error_interval).await?)
}
});
}
loop {
let mut widget = Widget::new().with_format(format.clone());
let mut info = PrivacyInfo::default();
for driver_info in try_join_all(drivers.iter_mut().map(|driver| driver.get_info())).await? {
for (type_, mapping) in driver_info {
let existing_mapping = info.entry(type_).or_default();
for (source, dest) in mapping.0 {
existing_mapping.entry(source).or_default().extend(dest);
}
}
}
if !info.is_empty() {
widget.state = State::Warning;
}
let mut values = Values::new();
if let Some(info_by_type) = info.get(&Type::Audio) {
map! { @extend values
"icon_audio" => Value::icon("microphone"),
"info_audio" => Value::text(info_by_type.to_string())
}
}
if let Some(info_by_type) = info.get(&Type::AudioSink) {
map! { @extend values
"icon_audio_sink" => Value::icon("volume"),
"info_audio_sink" => Value::text(info_by_type.to_string())
}
}
if let Some(info_by_type) = info.get(&Type::Video) {
map! { @extend values
"icon_video" => Value::icon("xrandr"),
"info_video" => Value::text(info_by_type.to_string())
}
}
if let Some(info_by_type) = info.get(&Type::Webcam) {
map! { @extend values
"icon_webcam" => Value::icon("webcam"),
"info_webcam" => Value::text(info_by_type.to_string())
}
}
if let Some(info_by_type) = info.get(&Type::Unknown) {
map! { @extend values
"icon_unknown" => Value::icon("unknown"),
"info_unknown" => Value::text(info_by_type.to_string())
}
}
widget.set_values(values);
api.set_widget(widget)?;
select! {
_ = api.wait_for_update_request() => (),
_ = select_all(drivers.iter_mut().map(|driver| driver.wait_for_change())) =>(),
Some(action) = actions.recv() => match action.as_ref() {
"toggle_format" => {
std::mem::swap(&mut format_alt, &mut format);
}
_ => (),
}
}
}
}