Skip to content

Commit 9a9bdb4

Browse files
authored
Support enabling multiple notification sinks (#344)
1 parent e40a8a8 commit 9a9bdb4

File tree

4 files changed

+106
-18
lines changed

4 files changed

+106
-18
lines changed

docs/iamb.5

+2
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,8 @@ to use the desktop mechanism (default).
269269
Setting this field to
270270
.Dq Sy bell
271271
will use the terminal bell instead.
272+
Both can be used via
273+
.Dq Sy desktop|bell .
272274

273275
.It Sy show_message
274276
controls whether to show the message in the desktop notification, and defaults to

src/config.rs

+79-9
Original file line numberDiff line numberDiff line change
@@ -398,23 +398,70 @@ pub enum UserDisplayStyle {
398398
DisplayName,
399399
}
400400

401-
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
402-
#[serde(rename_all = "lowercase")]
403-
pub enum NotifyVia {
401+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
402+
pub struct NotifyVia {
404403
/// Deliver notifications via terminal bell.
405-
Bell,
404+
pub bell: bool,
406405
/// Deliver notifications via desktop mechanism.
407406
#[cfg(feature = "desktop")]
408-
Desktop,
407+
pub desktop: bool,
409408
}
409+
pub struct NotifyViaVisitor;
410410

411411
impl Default for NotifyVia {
412412
fn default() -> Self {
413-
#[cfg(not(feature = "desktop"))]
414-
return NotifyVia::Bell;
413+
Self {
414+
bell: cfg!(not(feature = "desktop")),
415+
#[cfg(feature = "desktop")]
416+
desktop: true,
417+
}
418+
}
419+
}
420+
421+
impl<'de> Visitor<'de> for NotifyViaVisitor {
422+
type Value = NotifyVia;
423+
424+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
425+
formatter.write_str("a valid notify destination (e.g. \"bell\" or \"desktop\")")
426+
}
427+
428+
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
429+
where
430+
E: SerdeError,
431+
{
432+
let mut via = NotifyVia {
433+
bell: false,
434+
#[cfg(feature = "desktop")]
435+
desktop: false,
436+
};
415437

416-
#[cfg(feature = "desktop")]
417-
return NotifyVia::Desktop;
438+
for value in value.split('|') {
439+
match value.to_ascii_lowercase().as_str() {
440+
"bell" => {
441+
via.bell = true;
442+
},
443+
#[cfg(feature = "desktop")]
444+
"desktop" => {
445+
via.desktop = true;
446+
},
447+
#[cfg(not(feature = "desktop"))]
448+
"desktop" => {
449+
return Err(E::custom("desktop notification support was compiled out"))
450+
},
451+
_ => return Err(E::custom("could not parse into a notify destination")),
452+
};
453+
}
454+
455+
Ok(via)
456+
}
457+
}
458+
459+
impl<'de> Deserialize<'de> for NotifyVia {
460+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
461+
where
462+
D: Deserializer<'de>,
463+
{
464+
deserializer.deserialize_str(NotifyViaVisitor)
418465
}
419466
}
420467

@@ -1189,6 +1236,29 @@ mod tests {
11891236
assert_eq!(run, &exp);
11901237
}
11911238

1239+
#[test]
1240+
fn test_parse_notify_via() {
1241+
assert_eq!(NotifyVia { bell: false, desktop: true }, NotifyVia::default());
1242+
assert_eq!(
1243+
NotifyVia { bell: false, desktop: true },
1244+
serde_json::from_str(r#""desktop""#).unwrap()
1245+
);
1246+
assert_eq!(
1247+
NotifyVia { bell: true, desktop: false },
1248+
serde_json::from_str(r#""bell""#).unwrap()
1249+
);
1250+
assert_eq!(
1251+
NotifyVia { bell: true, desktop: true },
1252+
serde_json::from_str(r#""bell|desktop""#).unwrap()
1253+
);
1254+
assert_eq!(
1255+
NotifyVia { bell: true, desktop: true },
1256+
serde_json::from_str(r#""desktop|bell""#).unwrap()
1257+
);
1258+
assert!(serde_json::from_str::<NotifyVia>(r#""other""#).is_err());
1259+
assert!(serde_json::from_str::<NotifyVia>(r#""""#).is_err());
1260+
}
1261+
11921262
#[test]
11931263
fn test_load_example_config_toml() {
11941264
let path = PathBuf::from("config.example.toml");

src/notifications.rs

+24-8
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,7 @@ pub async fn register_notifications(
6363
return;
6464
}
6565

66-
match notify_via {
67-
#[cfg(feature = "desktop")]
68-
NotifyVia::Desktop => send_notification_desktop(summary, body),
69-
NotifyVia::Bell => send_notification_bell(&store).await,
70-
}
66+
send_notification(&notify_via, &store, &summary, body.as_deref()).await;
7167
},
7268
Err(err) => {
7369
tracing::error!("Failed to extract notification data: {err}")
@@ -78,22 +74,42 @@ pub async fn register_notifications(
7874
.await;
7975
}
8076

77+
async fn send_notification(
78+
via: &NotifyVia,
79+
store: &AsyncProgramStore,
80+
summary: &str,
81+
body: Option<&str>,
82+
) {
83+
#[cfg(feature = "desktop")]
84+
if via.desktop {
85+
send_notification_desktop(summary, body);
86+
}
87+
#[cfg(not(feature = "desktop"))]
88+
{
89+
let _ = (summary, body, IAMB_XDG_NAME);
90+
}
91+
92+
if via.bell {
93+
send_notification_bell(store).await;
94+
}
95+
}
96+
8197
async fn send_notification_bell(store: &AsyncProgramStore) {
8298
let mut locked = store.lock().await;
8399
locked.application.ring_bell = true;
84100
}
85101

86102
#[cfg(feature = "desktop")]
87-
fn send_notification_desktop(summary: String, body: Option<String>) {
103+
fn send_notification_desktop(summary: &str, body: Option<&str>) {
88104
let mut desktop_notification = notify_rust::Notification::new();
89105
desktop_notification
90-
.summary(&summary)
106+
.summary(summary)
91107
.appname(IAMB_XDG_NAME)
92108
.icon(IAMB_XDG_NAME)
93109
.action("default", "default");
94110

95111
if let Some(body) = body {
96-
desktop_notification.body(&body);
112+
desktop_notification.body(body);
97113
}
98114

99115
if let Err(err) = desktop_notification.show() {

src/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ pub fn mock_tunables() -> TunableValues {
191191
message_user_color: false,
192192
notifications: Notifications {
193193
enabled: false,
194-
via: NotifyVia::Desktop,
194+
via: NotifyVia::default(),
195195
show_message: true,
196196
},
197197
image_preview: None,

0 commit comments

Comments
 (0)