Support enabling multiple notification sinks (#344)

This commit is contained in:
Nemo157 2024-09-17 07:15:36 +02:00 committed by GitHub
parent e40a8a8d2e
commit 9a9bdb4862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 106 additions and 18 deletions

View file

@ -269,6 +269,8 @@ to use the desktop mechanism (default).
Setting this field to Setting this field to
.Dq Sy bell .Dq Sy bell
will use the terminal bell instead. will use the terminal bell instead.
Both can be used via
.Dq Sy desktop|bell .
.It Sy show_message .It Sy show_message
controls whether to show the message in the desktop notification, and defaults to controls whether to show the message in the desktop notification, and defaults to

View file

@ -398,23 +398,70 @@ pub enum UserDisplayStyle {
DisplayName, DisplayName,
} }
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[serde(rename_all = "lowercase")] pub struct NotifyVia {
pub enum NotifyVia {
/// Deliver notifications via terminal bell. /// Deliver notifications via terminal bell.
Bell, pub bell: bool,
/// Deliver notifications via desktop mechanism. /// Deliver notifications via desktop mechanism.
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
Desktop, pub desktop: bool,
} }
pub struct NotifyViaVisitor;
impl Default for NotifyVia { impl Default for NotifyVia {
fn default() -> Self { fn default() -> Self {
#[cfg(not(feature = "desktop"))] Self {
return NotifyVia::Bell; bell: cfg!(not(feature = "desktop")),
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
return NotifyVia::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<E>(self, value: &str) -> Result<Self::Value, E>
where
E: SerdeError,
{
let mut via = NotifyVia {
bell: false,
#[cfg(feature = "desktop")]
desktop: false,
};
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<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(NotifyViaVisitor)
} }
} }
@ -1189,6 +1236,29 @@ mod tests {
assert_eq!(run, &exp); 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::<NotifyVia>(r#""other""#).is_err());
assert!(serde_json::from_str::<NotifyVia>(r#""""#).is_err());
}
#[test] #[test]
fn test_load_example_config_toml() { fn test_load_example_config_toml() {
let path = PathBuf::from("config.example.toml"); let path = PathBuf::from("config.example.toml");

View file

@ -63,11 +63,7 @@ pub async fn register_notifications(
return; return;
} }
match notify_via { send_notification(&notify_via, &store, &summary, body.as_deref()).await;
#[cfg(feature = "desktop")]
NotifyVia::Desktop => send_notification_desktop(summary, body),
NotifyVia::Bell => send_notification_bell(&store).await,
}
}, },
Err(err) => { Err(err) => {
tracing::error!("Failed to extract notification data: {err}") tracing::error!("Failed to extract notification data: {err}")
@ -78,22 +74,42 @@ pub async fn register_notifications(
.await; .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) { async fn send_notification_bell(store: &AsyncProgramStore) {
let mut locked = store.lock().await; let mut locked = store.lock().await;
locked.application.ring_bell = true; locked.application.ring_bell = true;
} }
#[cfg(feature = "desktop")] #[cfg(feature = "desktop")]
fn send_notification_desktop(summary: String, body: Option<String>) { fn send_notification_desktop(summary: &str, body: Option<&str>) {
let mut desktop_notification = notify_rust::Notification::new(); let mut desktop_notification = notify_rust::Notification::new();
desktop_notification desktop_notification
.summary(&summary) .summary(summary)
.appname(IAMB_XDG_NAME) .appname(IAMB_XDG_NAME)
.icon(IAMB_XDG_NAME) .icon(IAMB_XDG_NAME)
.action("default", "default"); .action("default", "default");
if let Some(body) = body { if let Some(body) = body {
desktop_notification.body(&body); desktop_notification.body(body);
} }
if let Err(err) = desktop_notification.show() { if let Err(err) = desktop_notification.show() {

View file

@ -191,7 +191,7 @@ pub fn mock_tunables() -> TunableValues {
message_user_color: false, message_user_color: false,
notifications: Notifications { notifications: Notifications {
enabled: false, enabled: false,
via: NotifyVia::Desktop, via: NotifyVia::default(),
show_message: true, show_message: true,
}, },
image_preview: None, image_preview: None,