238 lines
7.3 KiB
Rust
238 lines
7.3 KiB
Rust
use gtk::{
|
|
gio,
|
|
glib::{self, clone},
|
|
prelude::*,
|
|
subclass::prelude::*,
|
|
};
|
|
use ruma::{
|
|
api::client::push::get_notifications::v3::Notification, EventId, OwnedEventId, OwnedRoomId,
|
|
RoomId,
|
|
};
|
|
use tracing::{debug, error, warn};
|
|
|
|
mod notifications_settings;
|
|
|
|
pub use self::notifications_settings::{
|
|
NotificationsGlobalSetting, NotificationsRoomSetting, NotificationsSettings,
|
|
};
|
|
use super::{Room, Session};
|
|
use crate::{
|
|
application::AppShowRoomPayload, prelude::*, spawn, spawn_tokio, utils::matrix::get_event_body,
|
|
Application, Window,
|
|
};
|
|
|
|
mod imp {
|
|
use std::{cell::RefCell, collections::HashMap};
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Default, glib::Properties)]
|
|
#[properties(wrapper_type = super::Notifications)]
|
|
pub struct Notifications {
|
|
/// The current session.
|
|
#[property(get, set = Self::set_session, explicit_notify, nullable)]
|
|
pub session: glib::WeakRef<Session>,
|
|
/// A map of room ID to list of event IDs for which a notification was
|
|
/// sent to the system.
|
|
pub list: RefCell<HashMap<OwnedRoomId, Vec<OwnedEventId>>>,
|
|
/// The notifications settings for this session.
|
|
#[property(get)]
|
|
pub settings: NotificationsSettings,
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for Notifications {
|
|
const NAME: &'static str = "Notifications";
|
|
type Type = super::Notifications;
|
|
}
|
|
|
|
#[glib::derived_properties]
|
|
impl ObjectImpl for Notifications {}
|
|
|
|
impl Notifications {
|
|
/// Set the current session.
|
|
fn set_session(&self, session: Option<&Session>) {
|
|
if self.session.upgrade().as_ref() == session {
|
|
return;
|
|
}
|
|
|
|
self.session.set(session);
|
|
self.obj().notify_session();
|
|
|
|
self.settings.set_session(session);
|
|
}
|
|
}
|
|
}
|
|
|
|
glib::wrapper! {
|
|
/// The notifications of a `Session`.
|
|
pub struct Notifications(ObjectSubclass<imp::Notifications>);
|
|
}
|
|
|
|
impl Notifications {
|
|
pub fn new() -> Self {
|
|
glib::Object::new()
|
|
}
|
|
|
|
/// Ask the system to show the given notification, if applicable.
|
|
///
|
|
/// The notification won't be shown if the application is active and this
|
|
/// session is displayed.
|
|
pub fn show(&self, matrix_notification: Notification) {
|
|
spawn!(clone!(@weak self as obj => async move {
|
|
obj.show_inner(matrix_notification).await;
|
|
}));
|
|
}
|
|
|
|
async fn show_inner(&self, matrix_notification: Notification) {
|
|
let Some(session) = self.session() else {
|
|
return;
|
|
};
|
|
|
|
// Don't show notifications if they are disabled.
|
|
if !session.settings().notifications_enabled() {
|
|
return;
|
|
}
|
|
|
|
let app = Application::default();
|
|
let window = app.active_window().and_downcast::<Window>();
|
|
let session_id = session.session_id();
|
|
|
|
// Don't show notifications for the current room in the current session if the
|
|
// window is active.
|
|
if window.is_some_and(|w| {
|
|
w.is_active()
|
|
&& w.current_session_id().as_deref() == Some(session_id)
|
|
&& w.session_view()
|
|
.selected_room()
|
|
.is_some_and(|r| r.room_id() == matrix_notification.room_id)
|
|
}) {
|
|
return;
|
|
}
|
|
|
|
let Some(room) = session.room_list().get(&matrix_notification.room_id) else {
|
|
warn!(
|
|
"Could not display notification for missing room {}",
|
|
matrix_notification.room_id
|
|
);
|
|
return;
|
|
};
|
|
|
|
let event = match matrix_notification.event.deserialize() {
|
|
Ok(event) => event,
|
|
Err(error) => {
|
|
warn!(
|
|
"Could not display notification for unrecognized event in room {}: {error}",
|
|
matrix_notification.room_id
|
|
);
|
|
return;
|
|
}
|
|
};
|
|
|
|
let matrix_room = room.matrix_room();
|
|
let sender_id = event.sender();
|
|
let owned_sender_id = sender_id.to_owned();
|
|
let handle =
|
|
spawn_tokio!(async move { matrix_room.get_member_no_sync(&owned_sender_id).await });
|
|
|
|
let sender = match handle.await.unwrap() {
|
|
Ok(member) => member,
|
|
Err(error) => {
|
|
error!("Failed to get member for notification: {error}");
|
|
None
|
|
}
|
|
};
|
|
|
|
let sender_name = sender
|
|
.as_ref()
|
|
.and_then(|m| m.display_name())
|
|
.unwrap_or_else(|| sender_id.localpart());
|
|
|
|
let body = match get_event_body(&event, sender_name) {
|
|
Some(body) => body,
|
|
None => {
|
|
debug!("Received notification for event of unexpected type {event:?}",);
|
|
return;
|
|
}
|
|
};
|
|
|
|
let room_id = room.room_id();
|
|
let event_id = event.event_id();
|
|
|
|
let notification = gio::Notification::new(&room.display_name());
|
|
notification.set_priority(gio::NotificationPriority::High);
|
|
|
|
let payload = AppShowRoomPayload {
|
|
session_id: session_id.to_owned(),
|
|
room_id: room_id.to_owned(),
|
|
};
|
|
|
|
notification
|
|
.set_default_action_and_target_value("app.show-room", Some(&payload.to_variant()));
|
|
notification.set_body(Some(&body));
|
|
|
|
if let Some(icon) = room.avatar_data().as_notification_icon() {
|
|
notification.set_icon(&icon);
|
|
}
|
|
|
|
let id = notification_id(session_id, room_id, event_id);
|
|
app.send_notification(Some(&id), ¬ification);
|
|
|
|
self.imp()
|
|
.list
|
|
.borrow_mut()
|
|
.entry(room_id.to_owned())
|
|
.or_default()
|
|
.push(event_id.to_owned());
|
|
}
|
|
|
|
/// Ask the system to remove the known notifications for the given room.
|
|
///
|
|
/// Only the notifications that were shown since the application's startup
|
|
/// are known, older ones might still be present.
|
|
pub fn withdraw_all_for_room(&self, room: &Room) {
|
|
let Some(session) = self.session() else {
|
|
return;
|
|
};
|
|
|
|
let room_id = room.room_id();
|
|
if let Some(notifications) = self.imp().list.borrow_mut().remove(room_id) {
|
|
let app = Application::default();
|
|
|
|
for event_id in notifications {
|
|
let id = notification_id(session.session_id(), room_id, &event_id);
|
|
app.withdraw_notification(&id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Ask the system to remove all the known notifications for this session.
|
|
///
|
|
/// Only the notifications that were shown since the application's startup
|
|
/// are known, older ones might still be present.
|
|
pub fn clear(&self) {
|
|
let Some(session) = self.session() else {
|
|
return;
|
|
};
|
|
|
|
let app = Application::default();
|
|
|
|
for (room_id, notifications) in self.imp().list.take() {
|
|
for event_id in notifications {
|
|
let id = notification_id(session.session_id(), &room_id, &event_id);
|
|
app.withdraw_notification(&id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Notifications {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
fn notification_id(session_id: &str, room_id: &RoomId, event_id: &EventId) -> String {
|
|
format!("{session_id}//{room_id}//{event_id}")
|
|
}
|