diff --git a/docs/iamb.5.md b/docs/iamb.5.md index adc2450..3d95776 100644 --- a/docs/iamb.5.md +++ b/docs/iamb.5.md @@ -90,9 +90,10 @@ overridden as described in *PROFILES*. > Defines whether or not the message body is colored like the username. **notifications** (type: notifications object) -> Configures push-notifications, which are delivered as desktop -> notifications if available. +> Configures push-notifications. > *enabled* `true` to enable the feature, defaults to `false`. +> *via* `"desktop"` to use desktop mechanism (default), or `"bell"` to use +> terminal bell. > *show_message* to show the message in the desktop notification. Defaults > to `true`. Messages are truncated beyond a small length. > The notification _rules_ are stored server side, loaded once at startup, diff --git a/src/base.rs b/src/base.rs index a306e16..7cb8137 100644 --- a/src/base.rs +++ b/src/base.rs @@ -1273,6 +1273,9 @@ pub struct ChatStore { /// Last draw time, used to match with RoomInfo's draw_last. pub draw_curr: Option, + + /// Whether to ring the terminal bell on the next redraw. + pub ring_bell: bool, } impl ChatStore { @@ -1294,6 +1297,7 @@ impl ChatStore { need_load: Default::default(), sync_info: Default::default(), draw_curr: None, + ring_bell: false, } } diff --git a/src/config.rs b/src/config.rs index f2e36aa..ab40850 100644 --- a/src/config.rs +++ b/src/config.rs @@ -86,6 +86,10 @@ fn is_profile_char(c: char) -> bool { c.is_ascii_alphanumeric() || c == '.' || c == '-' } +fn default_true() -> bool { + true +} + fn validate_profile_name(name: &str) -> bool { if name.is_empty() { return false; @@ -391,10 +395,24 @@ pub enum UserDisplayStyle { DisplayName, } +#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum NotifyVia { + /// Deliver notifications via terminal bell. + Bell, + /// Deliver notifications via desktop mechanism. + #[default] + Desktop, +} + #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)] pub struct Notifications { + #[serde(default)] pub enabled: bool, - pub show_message: Option, + #[serde(default)] + pub via: NotifyVia, + #[serde(default = "default_true")] + pub show_message: bool, } #[derive(Clone)] diff --git a/src/main.rs b/src/main.rs index c851c28..1782c80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ use std::collections::VecDeque; use std::convert::TryFrom; use std::fmt::Display; use std::fs::{create_dir_all, File}; -use std::io::{stdout, BufWriter, Stdout}; +use std::io::{stdout, BufWriter, Stdout, Write}; use std::ops::DerefMut; use std::process; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -293,6 +293,10 @@ impl Application { let sstate = &mut self.screen; let term = &mut self.terminal; + if store.application.ring_bell { + store.application.ring_bell = term.backend_mut().write_all(&[7]).is_err(); + } + if full { term.clear()?; } diff --git a/src/notifications.rs b/src/notifications.rs index ea63c3b..194e6a0 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -15,7 +15,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::{ base::{AsyncProgramStore, IambError, IambResult}, - config::ApplicationSettings, + config::{ApplicationSettings, NotifyVia}, }; pub async fn register_notifications( @@ -26,6 +26,7 @@ pub async fn register_notifications( if !settings.tunables.notifications.enabled { return; } + let notify_via = settings.tunables.notifications.via; let show_message = settings.tunables.notifications.show_message; let server_settings = client.notification_settings().await; let Some(startup_ts) = MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::now()) else { @@ -47,29 +48,19 @@ pub async fn register_notifications( return; } - match parse_notification(notification, room).await { + match parse_notification(notification, room, show_message).await { Ok((summary, body, server_ts)) => { if server_ts < startup_ts { return; } - let mut desktop_notification = notify_rust::Notification::new(); - desktop_notification - .summary(&summary) - .appname("iamb") - .timeout(notify_rust::Timeout::Milliseconds(3000)) - .action("default", "default"); - if is_missing_mention(&body, mode, &client) { return; } - if show_message != Some(false) { - if let Some(body) = body { - desktop_notification.body(&body); - } - } - if let Err(err) = desktop_notification.show() { - tracing::error!("Failed to send notification: {err}") + + match notify_via { + NotifyVia::Desktop => send_notification_desktop(summary, body), + NotifyVia::Bell => send_notification_bell(&store).await, } }, Err(err) => { @@ -81,6 +72,28 @@ pub async fn register_notifications( .await; } +async fn send_notification_bell(store: &AsyncProgramStore) { + let mut locked = store.lock().await; + locked.application.ring_bell = true; +} + +fn send_notification_desktop(summary: String, body: Option) { + let mut desktop_notification = notify_rust::Notification::new(); + desktop_notification + .summary(&summary) + .appname("iamb") + .timeout(notify_rust::Timeout::Milliseconds(3000)) + .action("default", "default"); + + if let Some(body) = body { + desktop_notification.body(&body); + } + + if let Err(err) = desktop_notification.show() { + tracing::error!("Failed to send notification: {err}") + } +} + async fn global_or_room_mode( settings: &NotificationSettings, room: &MatrixRoom, @@ -129,6 +142,7 @@ async fn is_open(store: &AsyncProgramStore, room_id: &RoomId) -> bool { pub async fn parse_notification( notification: Notification, room: MatrixRoom, + show_body: bool, ) -> IambResult<(String, Option, MilliSecondsSinceUnixEpoch)> { let event = notification.event.deserialize().map_err(IambError::from)?; @@ -142,12 +156,17 @@ pub async fn parse_notification( .and_then(|m| m.display_name()) .unwrap_or_else(|| sender_id.localpart()); - let body = event_notification_body( - &event, - sender_name, - room.is_direct().await.map_err(IambError::from)?, - ) - .map(truncate); + let body = if show_body { + event_notification_body( + &event, + sender_name, + room.is_direct().await.map_err(IambError::from)?, + ) + .map(truncate) + } else { + None + }; + return Ok((sender_name.to_string(), body, server_ts)); } diff --git a/src/tests.rs b/src/tests.rs index aa9284b..4d4de21 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -28,6 +28,7 @@ use crate::{ ApplicationSettings, DirectoryValues, Notifications, + NotifyVia, ProfileConfig, SortOverrides, TunableValues, @@ -187,7 +188,11 @@ pub fn mock_tunables() -> TunableValues { open_command: None, username_display: UserDisplayStyle::Username, message_user_color: false, - notifications: Notifications { enabled: false, show_message: None }, + notifications: Notifications { + enabled: false, + via: NotifyVia::Desktop, + show_message: true, + }, image_preview: None, user_gutter_width: 30, }