mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Support notifications via terminal bell (#227)
Co-authored-by: Benjamin Grosse <ste3ls@gmail.com>
This commit is contained in:
parent
db9cb92737
commit
99996e275b
6 changed files with 78 additions and 27 deletions
|
@ -90,9 +90,10 @@ overridden as described in *PROFILES*.
|
||||||
> Defines whether or not the message body is colored like the username.
|
> Defines whether or not the message body is colored like the username.
|
||||||
|
|
||||||
**notifications** (type: notifications object)
|
**notifications** (type: notifications object)
|
||||||
> Configures push-notifications, which are delivered as desktop
|
> Configures push-notifications.
|
||||||
> notifications if available.
|
|
||||||
> *enabled* `true` to enable the feature, defaults to `false`.
|
> *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
|
> *show_message* to show the message in the desktop notification. Defaults
|
||||||
> to `true`. Messages are truncated beyond a small length.
|
> to `true`. Messages are truncated beyond a small length.
|
||||||
> The notification _rules_ are stored server side, loaded once at startup,
|
> The notification _rules_ are stored server side, loaded once at startup,
|
||||||
|
|
|
@ -1273,6 +1273,9 @@ pub struct ChatStore {
|
||||||
|
|
||||||
/// Last draw time, used to match with RoomInfo's draw_last.
|
/// Last draw time, used to match with RoomInfo's draw_last.
|
||||||
pub draw_curr: Option<Instant>,
|
pub draw_curr: Option<Instant>,
|
||||||
|
|
||||||
|
/// Whether to ring the terminal bell on the next redraw.
|
||||||
|
pub ring_bell: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatStore {
|
impl ChatStore {
|
||||||
|
@ -1294,6 +1297,7 @@ impl ChatStore {
|
||||||
need_load: Default::default(),
|
need_load: Default::default(),
|
||||||
sync_info: Default::default(),
|
sync_info: Default::default(),
|
||||||
draw_curr: None,
|
draw_curr: None,
|
||||||
|
ring_bell: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,6 +86,10 @@ fn is_profile_char(c: char) -> bool {
|
||||||
c.is_ascii_alphanumeric() || c == '.' || c == '-'
|
c.is_ascii_alphanumeric() || c == '.' || c == '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_profile_name(name: &str) -> bool {
|
fn validate_profile_name(name: &str) -> bool {
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
|
@ -391,10 +395,24 @@ pub enum UserDisplayStyle {
|
||||||
DisplayName,
|
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)]
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||||
pub struct Notifications {
|
pub struct Notifications {
|
||||||
|
#[serde(default)]
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub show_message: Option<bool>,
|
#[serde(default)]
|
||||||
|
pub via: NotifyVia,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub show_message: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
|
@ -19,7 +19,7 @@ use std::collections::VecDeque;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::{create_dir_all, File};
|
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::ops::DerefMut;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
@ -293,6 +293,10 @@ impl Application {
|
||||||
let sstate = &mut self.screen;
|
let sstate = &mut self.screen;
|
||||||
let term = &mut self.terminal;
|
let term = &mut self.terminal;
|
||||||
|
|
||||||
|
if store.application.ring_bell {
|
||||||
|
store.application.ring_bell = term.backend_mut().write_all(&[7]).is_err();
|
||||||
|
}
|
||||||
|
|
||||||
if full {
|
if full {
|
||||||
term.clear()?;
|
term.clear()?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
base::{AsyncProgramStore, IambError, IambResult},
|
base::{AsyncProgramStore, IambError, IambResult},
|
||||||
config::ApplicationSettings,
|
config::{ApplicationSettings, NotifyVia},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn register_notifications(
|
pub async fn register_notifications(
|
||||||
|
@ -26,6 +26,7 @@ pub async fn register_notifications(
|
||||||
if !settings.tunables.notifications.enabled {
|
if !settings.tunables.notifications.enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let notify_via = settings.tunables.notifications.via;
|
||||||
let show_message = settings.tunables.notifications.show_message;
|
let show_message = settings.tunables.notifications.show_message;
|
||||||
let server_settings = client.notification_settings().await;
|
let server_settings = client.notification_settings().await;
|
||||||
let Some(startup_ts) = MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::now()) else {
|
let Some(startup_ts) = MilliSecondsSinceUnixEpoch::from_system_time(SystemTime::now()) else {
|
||||||
|
@ -47,29 +48,19 @@ pub async fn register_notifications(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match parse_notification(notification, room).await {
|
match parse_notification(notification, room, show_message).await {
|
||||||
Ok((summary, body, server_ts)) => {
|
Ok((summary, body, server_ts)) => {
|
||||||
if server_ts < startup_ts {
|
if server_ts < startup_ts {
|
||||||
return;
|
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) {
|
if is_missing_mention(&body, mode, &client) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if show_message != Some(false) {
|
|
||||||
if let Some(body) = body {
|
match notify_via {
|
||||||
desktop_notification.body(&body);
|
NotifyVia::Desktop => send_notification_desktop(summary, body),
|
||||||
}
|
NotifyVia::Bell => send_notification_bell(&store).await,
|
||||||
}
|
|
||||||
if let Err(err) = desktop_notification.show() {
|
|
||||||
tracing::error!("Failed to send notification: {err}")
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -81,6 +72,28 @@ pub async fn register_notifications(
|
||||||
.await;
|
.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(
|
async fn global_or_room_mode(
|
||||||
settings: &NotificationSettings,
|
settings: &NotificationSettings,
|
||||||
room: &MatrixRoom,
|
room: &MatrixRoom,
|
||||||
|
@ -129,6 +142,7 @@ async fn is_open(store: &AsyncProgramStore, room_id: &RoomId) -> bool {
|
||||||
pub async fn parse_notification(
|
pub async fn parse_notification(
|
||||||
notification: Notification,
|
notification: Notification,
|
||||||
room: MatrixRoom,
|
room: MatrixRoom,
|
||||||
|
show_body: bool,
|
||||||
) -> IambResult<(String, Option<String>, MilliSecondsSinceUnixEpoch)> {
|
) -> IambResult<(String, Option<String>, MilliSecondsSinceUnixEpoch)> {
|
||||||
let event = notification.event.deserialize().map_err(IambError::from)?;
|
let event = notification.event.deserialize().map_err(IambError::from)?;
|
||||||
|
|
||||||
|
@ -142,12 +156,17 @@ pub async fn parse_notification(
|
||||||
.and_then(|m| m.display_name())
|
.and_then(|m| m.display_name())
|
||||||
.unwrap_or_else(|| sender_id.localpart());
|
.unwrap_or_else(|| sender_id.localpart());
|
||||||
|
|
||||||
let body = event_notification_body(
|
let body = if show_body {
|
||||||
|
event_notification_body(
|
||||||
&event,
|
&event,
|
||||||
sender_name,
|
sender_name,
|
||||||
room.is_direct().await.map_err(IambError::from)?,
|
room.is_direct().await.map_err(IambError::from)?,
|
||||||
)
|
)
|
||||||
.map(truncate);
|
.map(truncate)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
return Ok((sender_name.to_string(), body, server_ts));
|
return Ok((sender_name.to_string(), body, server_ts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
||||||
ApplicationSettings,
|
ApplicationSettings,
|
||||||
DirectoryValues,
|
DirectoryValues,
|
||||||
Notifications,
|
Notifications,
|
||||||
|
NotifyVia,
|
||||||
ProfileConfig,
|
ProfileConfig,
|
||||||
SortOverrides,
|
SortOverrides,
|
||||||
TunableValues,
|
TunableValues,
|
||||||
|
@ -187,7 +188,11 @@ pub fn mock_tunables() -> TunableValues {
|
||||||
open_command: None,
|
open_command: None,
|
||||||
username_display: UserDisplayStyle::Username,
|
username_display: UserDisplayStyle::Username,
|
||||||
message_user_color: false,
|
message_user_color: false,
|
||||||
notifications: Notifications { enabled: false, show_message: None },
|
notifications: Notifications {
|
||||||
|
enabled: false,
|
||||||
|
via: NotifyVia::Desktop,
|
||||||
|
show_message: true,
|
||||||
|
},
|
||||||
image_preview: None,
|
image_preview: None,
|
||||||
user_gutter_width: 30,
|
user_gutter_width: 30,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue