Support notifications via terminal bell (#227)

Co-authored-by: Benjamin Grosse <ste3ls@gmail.com>
This commit is contained in:
Ulyssa 2024-03-24 10:19:34 -07:00 committed by GitHub
parent db9cb92737
commit 99996e275b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 78 additions and 27 deletions

View file

@ -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,

View file

@ -1273,6 +1273,9 @@ pub struct ChatStore {
/// Last draw time, used to match with RoomInfo's draw_last.
pub draw_curr: Option<Instant>,
/// 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,
}
}

View file

@ -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<bool>,
#[serde(default)]
pub via: NotifyVia,
#[serde(default = "default_true")]
pub show_message: bool,
}
#[derive(Clone)]

View file

@ -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()?;
}

View file

@ -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<String>) {
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<String>, 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));
}

View file

@ -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,
}