From 9a9bdb4862646da3b03490ab250feb62df7ea468 Mon Sep 17 00:00:00 2001 From: Nemo157 Date: Tue, 17 Sep 2024 07:15:36 +0200 Subject: [PATCH] Support enabling multiple notification sinks (#344) --- docs/iamb.5 | 2 + src/config.rs | 88 +++++++++++++++++++++++++++++++++++++++----- src/notifications.rs | 32 ++++++++++++---- src/tests.rs | 2 +- 4 files changed, 106 insertions(+), 18 deletions(-) diff --git a/docs/iamb.5 b/docs/iamb.5 index 90d86de..dc288b3 100644 --- a/docs/iamb.5 +++ b/docs/iamb.5 @@ -269,6 +269,8 @@ to use the desktop mechanism (default). Setting this field to .Dq Sy bell will use the terminal bell instead. +Both can be used via +.Dq Sy desktop|bell . .It Sy show_message controls whether to show the message in the desktop notification, and defaults to diff --git a/src/config.rs b/src/config.rs index 584ac9c..77e874a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -398,23 +398,70 @@ pub enum UserDisplayStyle { DisplayName, } -#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum NotifyVia { +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct NotifyVia { /// Deliver notifications via terminal bell. - Bell, + pub bell: bool, /// Deliver notifications via desktop mechanism. #[cfg(feature = "desktop")] - Desktop, + pub desktop: bool, } +pub struct NotifyViaVisitor; impl Default for NotifyVia { fn default() -> Self { - #[cfg(not(feature = "desktop"))] - return NotifyVia::Bell; + Self { + bell: cfg!(not(feature = "desktop")), + #[cfg(feature = "desktop")] + desktop: true, + } + } +} + +impl<'de> Visitor<'de> for NotifyViaVisitor { + type Value = NotifyVia; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a valid notify destination (e.g. \"bell\" or \"desktop\")") + } + + fn visit_str(self, value: &str) -> Result + where + E: SerdeError, + { + let mut via = NotifyVia { + bell: false, + #[cfg(feature = "desktop")] + desktop: false, + }; - #[cfg(feature = "desktop")] - return NotifyVia::Desktop; + for value in value.split('|') { + match value.to_ascii_lowercase().as_str() { + "bell" => { + via.bell = true; + }, + #[cfg(feature = "desktop")] + "desktop" => { + via.desktop = true; + }, + #[cfg(not(feature = "desktop"))] + "desktop" => { + return Err(E::custom("desktop notification support was compiled out")) + }, + _ => return Err(E::custom("could not parse into a notify destination")), + }; + } + + Ok(via) + } +} + +impl<'de> Deserialize<'de> for NotifyVia { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_str(NotifyViaVisitor) } } @@ -1189,6 +1236,29 @@ mod tests { assert_eq!(run, &exp); } + #[test] + fn test_parse_notify_via() { + assert_eq!(NotifyVia { bell: false, desktop: true }, NotifyVia::default()); + assert_eq!( + NotifyVia { bell: false, desktop: true }, + serde_json::from_str(r#""desktop""#).unwrap() + ); + assert_eq!( + NotifyVia { bell: true, desktop: false }, + serde_json::from_str(r#""bell""#).unwrap() + ); + assert_eq!( + NotifyVia { bell: true, desktop: true }, + serde_json::from_str(r#""bell|desktop""#).unwrap() + ); + assert_eq!( + NotifyVia { bell: true, desktop: true }, + serde_json::from_str(r#""desktop|bell""#).unwrap() + ); + assert!(serde_json::from_str::(r#""other""#).is_err()); + assert!(serde_json::from_str::(r#""""#).is_err()); + } + #[test] fn test_load_example_config_toml() { let path = PathBuf::from("config.example.toml"); diff --git a/src/notifications.rs b/src/notifications.rs index f6cf3e3..32762d4 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -63,11 +63,7 @@ pub async fn register_notifications( return; } - match notify_via { - #[cfg(feature = "desktop")] - NotifyVia::Desktop => send_notification_desktop(summary, body), - NotifyVia::Bell => send_notification_bell(&store).await, - } + send_notification(¬ify_via, &store, &summary, body.as_deref()).await; }, Err(err) => { tracing::error!("Failed to extract notification data: {err}") @@ -78,22 +74,42 @@ pub async fn register_notifications( .await; } +async fn send_notification( + via: &NotifyVia, + store: &AsyncProgramStore, + summary: &str, + body: Option<&str>, +) { + #[cfg(feature = "desktop")] + if via.desktop { + send_notification_desktop(summary, body); + } + #[cfg(not(feature = "desktop"))] + { + let _ = (summary, body, IAMB_XDG_NAME); + } + + if via.bell { + send_notification_bell(store).await; + } +} + async fn send_notification_bell(store: &AsyncProgramStore) { let mut locked = store.lock().await; locked.application.ring_bell = true; } #[cfg(feature = "desktop")] -fn send_notification_desktop(summary: String, body: Option) { +fn send_notification_desktop(summary: &str, body: Option<&str>) { let mut desktop_notification = notify_rust::Notification::new(); desktop_notification - .summary(&summary) + .summary(summary) .appname(IAMB_XDG_NAME) .icon(IAMB_XDG_NAME) .action("default", "default"); if let Some(body) = body { - desktop_notification.body(&body); + desktop_notification.body(body); } if let Err(err) = desktop_notification.show() { diff --git a/src/tests.rs b/src/tests.rs index b385992..fa379de 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -191,7 +191,7 @@ pub fn mock_tunables() -> TunableValues { message_user_color: false, notifications: Notifications { enabled: false, - via: NotifyVia::Desktop, + via: NotifyVia::default(), show_message: true, }, image_preview: None,