You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
222 lines
6.7 KiB
222 lines
6.7 KiB
use gtk::{gio, glib, prelude::*, subclass::prelude::*}; |
|
use log::{debug, warn}; |
|
use ruma::{ |
|
api::client::push::get_notifications::v3::Notification, EventId, OwnedEventId, OwnedRoomId, |
|
RoomId, |
|
}; |
|
|
|
use super::{Room, Session}; |
|
use crate::{ |
|
application::AppShowRoomPayload, prelude::*, utils::matrix::get_event_body, Application, |
|
}; |
|
|
|
mod imp { |
|
use std::{cell::RefCell, collections::HashMap}; |
|
|
|
use glib::WeakRef; |
|
use once_cell::sync::Lazy; |
|
|
|
use super::*; |
|
|
|
#[derive(Debug, Default)] |
|
pub struct Notifications { |
|
pub session: 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>>>, |
|
} |
|
|
|
#[glib::object_subclass] |
|
impl ObjectSubclass for Notifications { |
|
const NAME: &'static str = "Notifications"; |
|
type Type = super::Notifications; |
|
} |
|
|
|
impl ObjectImpl for Notifications { |
|
fn properties() -> &'static [glib::ParamSpec] { |
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| { |
|
vec![glib::ParamSpecObject::builder::<Session>("session") |
|
.explicit_notify() |
|
.build()] |
|
}); |
|
|
|
PROPERTIES.as_ref() |
|
} |
|
|
|
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { |
|
match pspec.name() { |
|
"session" => self.obj().set_session(value.get().unwrap()), |
|
_ => unimplemented!(), |
|
} |
|
} |
|
|
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { |
|
match pspec.name() { |
|
"session" => self.obj().session().to_value(), |
|
_ => unimplemented!(), |
|
} |
|
} |
|
} |
|
} |
|
|
|
glib::wrapper! { |
|
/// The notifications of a `Session`. |
|
pub struct Notifications(ObjectSubclass<imp::Notifications>); |
|
} |
|
|
|
impl Notifications { |
|
pub fn new() -> Self { |
|
glib::Object::new() |
|
} |
|
|
|
/// The current session. |
|
pub fn session(&self) -> Option<Session> { |
|
self.imp().session.upgrade() |
|
} |
|
|
|
/// Set the current session. |
|
pub fn set_session(&self, session: Option<&Session>) { |
|
let imp = self.imp(); |
|
|
|
if self.session().as_ref() == session { |
|
return; |
|
} |
|
|
|
imp.session.set(session); |
|
self.notify("session"); |
|
} |
|
|
|
/// 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) { |
|
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.main_window(); |
|
let session_id = session.session_id(); |
|
|
|
// Don't show notifications for the current session if the window is active. |
|
if window.is_active() && window.current_session_id().as_deref() == Some(session_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 sender_name = room |
|
.members() |
|
.member_by_id(event.sender().to_owned()) |
|
.display_name(); |
|
|
|
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); |
|
Application::default().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}") |
|
}
|
|
|