diff --git a/src/base.rs b/src/base.rs index 56d4c2d..bb4f491 100644 --- a/src/base.rs +++ b/src/base.rs @@ -92,6 +92,7 @@ use modalkit::{ use crate::config::ImagePreviewProtocolValues; use crate::message::ImageStatus; +use crate::notifications::NotificationHandle; use crate::preview::{source_from_event, spawn_insert_preview}; use crate::{ message::{Message, MessageEvent, MessageKey, MessageTimeStamp, Messages}, @@ -1558,6 +1559,9 @@ pub struct ChatStore { /// Collator for locale-aware text sorting. pub collator: feruca::Collator, + + /// Notifications that should be dismissed when the user opens the room. + pub open_notifications: HashMap>, } impl ChatStore { @@ -1582,6 +1586,7 @@ impl ChatStore { draw_curr: None, ring_bell: false, focused: true, + open_notifications: Default::default(), } } diff --git a/src/main.rs b/src/main.rs index b0a0eec..cee2247 100644 --- a/src/main.rs +++ b/src/main.rs @@ -561,6 +561,9 @@ impl Application { IambAction::ClearUnreads => { let user_id = &store.application.settings.profile.user_id; + // Clear any notifications we displayed: + store.application.open_notifications.clear(); + for room_id in store.application.sync_info.chats() { if let Some(room) = store.application.rooms.get_mut(room_id) { room.fully_read(user_id); diff --git a/src/notifications.rs b/src/notifications.rs index c120222..0462101 100644 --- a/src/notifications.rs +++ b/src/notifications.rs @@ -8,6 +8,7 @@ use matrix_sdk::{ events::{room::message::MessageType, AnyMessageLikeEventContent, AnySyncTimelineEvent}, serde::Raw, MilliSecondsSinceUnixEpoch, + OwnedRoomId, RoomId, }, Client, @@ -24,6 +25,21 @@ const IAMB_XDG_NAME: &str = match option_env!("IAMB_XDG_NAME") { Some(iamb) => iamb, }; +/// Handle for an open notification that should be closed when the user views it. +pub struct NotificationHandle( + #[cfg(all(feature = "desktop", unix, not(target_os = "macos")))] + Option, +); + +impl Drop for NotificationHandle { + fn drop(&mut self) { + #[cfg(all(feature = "desktop", unix, not(target_os = "macos")))] + if let Some(handle) = self.0.take() { + handle.close(); + } + } +} + pub async fn register_notifications( client: &Client, settings: &ApplicationSettings, @@ -54,6 +70,7 @@ pub async fn register_notifications( return; } + let room_id = room.room_id().to_owned(); match notification.event { RawAnySyncOrStrippedTimelineEvent::Sync(e) => { match parse_full_notification(e, room, show_message).await { @@ -66,8 +83,14 @@ pub async fn register_notifications( return; } - send_notification(¬ify_via, &store, &summary, body.as_deref()) - .await; + send_notification( + ¬ify_via, + &summary, + body.as_deref(), + room_id, + &store, + ) + .await; }, Err(err) => { tracing::error!("Failed to extract notification data: {err}") @@ -86,13 +109,14 @@ pub async fn register_notifications( async fn send_notification( via: &NotifyVia, - store: &AsyncProgramStore, summary: &str, body: Option<&str>, + room_id: OwnedRoomId, + store: &AsyncProgramStore, ) { #[cfg(feature = "desktop")] if via.desktop { - send_notification_desktop(summary, body); + send_notification_desktop(summary, body, room_id, store).await; } #[cfg(not(feature = "desktop"))] { @@ -110,7 +134,12 @@ async fn send_notification_bell(store: &AsyncProgramStore) { } #[cfg(feature = "desktop")] -fn send_notification_desktop(summary: &str, body: Option<&str>) { +async fn send_notification_desktop( + summary: &str, + body: Option<&str>, + room_id: OwnedRoomId, + _store: &AsyncProgramStore, +) { let mut desktop_notification = notify_rust::Notification::new(); desktop_notification .summary(summary) @@ -125,8 +154,19 @@ fn send_notification_desktop(summary: &str, body: Option<&str>) { desktop_notification.body(body); } - if let Err(err) = desktop_notification.show() { - tracing::error!("Failed to send notification: {err}") + match desktop_notification.show() { + Err(err) => tracing::error!("Failed to send notification: {err}"), + Ok(handle) => { + #[cfg(all(unix, not(target_os = "macos")))] + _store + .lock() + .await + .application + .open_notifications + .entry(room_id) + .or_default() + .push(NotificationHandle(Some(handle))); + }, } } diff --git a/src/worker.rs b/src/worker.rs index 144c34c..d0392d1 100644 --- a/src/worker.rs +++ b/src/worker.rs @@ -502,19 +502,23 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) { loop { interval.tick().await; - let locked = store.lock().await; - let user_id = &locked.application.settings.profile.user_id; + let mut locked = store.lock().await; + let ChatStore { settings, open_notifications, rooms, .. } = &mut locked.application; + let user_id = &settings.profile.user_id; let mut updates = Vec::new(); for room in client.joined_rooms() { let room_id = room.room_id(); - let Some(info) = locked.application.rooms.get(&room_id) else { + let Some(info) = rooms.get(room_id) else { continue; }; let changed = info.receipts(user_id).filter_map(|(thread, new_receipt)| { let old_receipt = sent.get(room_id).and_then(|ts| ts.get(thread)); let changed = Some(new_receipt) != old_receipt; + if changed { + open_notifications.remove(room_id); + } changed.then(|| (room_id.to_owned(), thread.to_owned(), new_receipt.to_owned())) });