user_notify/platform_impl/xdg/
mod.rs1mod category;
4
5use std::{
6 collections::HashMap,
7 sync::{Arc, OnceLock},
8};
9
10use async_trait::async_trait;
11use image::ImageReader;
12use notify_rust::{ActionResponse, CloseReason, Hint, Urgency, handle_action};
13use tokio::sync::RwLock;
14
15use crate::{NotificationBuilder, NotificationHandle, NotificationManager, NotificationResponse};
16
17#[derive(Debug, Clone)]
19pub struct NotificationHandleXdg {
20 id: String,
21 user_info: HashMap<String, String>,
22 handle: Arc<RwLock<Option<notify_rust::NotificationHandle>>>,
23}
24
25impl NotificationHandle for NotificationHandleXdg {
26 fn close(&self) -> Result<(), crate::Error> {
27 log::info!("called close notification handle {self:?}");
28 Ok(())
29 }
30
31 fn get_id(&self) -> String {
32 self.id.clone()
33 }
34
35 fn get_user_info(&self) -> &HashMap<String, String> {
36 &self.user_info
37 }
38}
39
40#[derive(Default)]
42pub struct NotificationManagerXdg {
43 active_notifications: RwLock<Vec<NotificationHandleXdg>>,
44 #[allow(clippy::type_complexity)]
45 handler: OnceLock<Arc<Box<dyn Fn(crate::NotificationResponse) + Send + Sync + 'static>>>,
46}
47
48impl std::fmt::Debug for NotificationManagerXdg {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 f.debug_struct("NotificationManagerXdg")
51 .field("active_notifications", &self.active_notifications)
52 .field("handler", &self.handler.get().is_some().to_string())
53 .finish()
54 }
55}
56
57impl NotificationManagerXdg {
58 pub fn new() -> Self {
60 Self::default()
61 }
62
63 async fn add_notification(&self, notification: NotificationHandleXdg) {
64 self.active_notifications.write().await.push(notification);
65 }
66}
67
68#[async_trait]
69impl NotificationManager for NotificationManagerXdg {
70 async fn get_notification_permission_state(&self) -> Result<bool, crate::Error> {
71 log::info!(
72 "NotificationManagerXdg::get_notification_permission_state: not implemented yet"
73 );
74
75 Ok(true)
76 }
77
78 async fn first_time_ask_for_notification_permission(&self) -> Result<bool, crate::Error> {
79 log::info!(
80 "NotificationManagerXdg::first_time_ask_for_notification_permission: not implemented yet"
81 );
82 Ok(true)
83 }
84
85 fn register(
86 &self,
87 handler_callback: Box<dyn Fn(crate::NotificationResponse) + Send + Sync + 'static>,
88 categories: Vec<crate::NotificationCategory>,
89 ) -> Result<(), crate::Error> {
90 log::info!("NotificationManagerXdg::register {categories:?}");
91
92 let _ = self.handler.set(Arc::new(handler_callback));
93
94 Ok(())
99 }
100
101 fn remove_all_delivered_notifications(&self) -> Result<(), crate::Error> {
102 let mut active_notifications = self.active_notifications.try_write()?;
103 let removed_notifications = active_notifications.drain(..);
104
105 for notification in removed_notifications {
106 if let Some(handle) = notification.handle.try_write()?.take() {
107 handle.close();
108 } else {
109 log::error!("handle is not there anymore");
110 }
111 }
112
113 Ok(())
114 }
115
116 fn remove_delivered_notifications(&self, ids: Vec<&str>) -> Result<(), crate::Error> {
117 let mut active_notifications = self.active_notifications.try_write()?;
118 let all_notifications = active_notifications.drain(..);
119 let mut kept = Vec::new();
120 let mut removed = Vec::new();
121 for n in all_notifications {
122 if ids.contains(&n.id.as_str()) {
123 removed.push(n);
124 } else {
125 kept.push(n);
126 }
127 }
128 active_notifications.append(&mut kept);
129
130 for notification in removed {
131 if let Some(handle) = notification.handle.try_write()?.take() {
132 handle.close();
133 } else {
134 log::error!("handle is not there anymore");
135 }
136 }
137
138 Ok(())
139 }
140
141 async fn get_active_notifications(
142 &self,
143 ) -> Result<Vec<Box<dyn NotificationHandle>>, crate::Error> {
144 let active_notifications = self.active_notifications.read().await;
146 Ok(active_notifications
147 .clone()
148 .into_iter()
149 .map(|n| Box::new(n) as Box<dyn NotificationHandle>)
150 .collect())
151 }
152
153 async fn send_notification(
154 &self,
155 builder: NotificationBuilder,
156 ) -> Result<Box<dyn NotificationHandle>, crate::Error> {
157 log::info!("show notification {self:?}");
158 let id = uuid::Uuid::new_v4().to_string();
159
160 let mut notification = notify_rust::Notification::new();
161
162 notification.hint(Hint::Urgency(notify_rust::Urgency::Normal));
164 notification.hint(Hint::Resident(true));
165 if let Some(xdg_app_name) = builder.xdg_app_name {
166 notification.appname(&xdg_app_name);
167 }
168
169 if let Some(body) = builder.body {
170 notification.body(&quick_xml::escape::escape(body));
171 }
172
173 if let Some(title) = builder.title {
174 notification.summary(&title);
175 }
176
177 if let Some(path) = builder.image {
180 match ImageReader::open(path) {
181 Err(error) => {
182 log::error!("failed to load image: {error:?}");
183 }
184 Ok(img) => match img.decode() {
185 Err(error) => {
186 log::error!("failed to decode image: {error:?}");
187 }
188 Ok(img_data) => {
189 let thumbnail = img_data.thumbnail(512, 512);
190 match thumbnail.try_into() {
191 Err(error) => log::error!("failed to convert image: {error:?}"),
192 Ok(img) => {
193 notification.hint(Hint::ImageData(img));
194 }
195 }
196 }
197 },
198 }
199 }
200
201 if let Some(path) = builder.icon {
202 notification.icon(&format!("file://{}", path.display()));
204 } else {
205 notification.auto_icon();
206 }
207
208 if let Some(_thread_id) = builder.thread_id {
209 }
212
213 if let Some(_category_id) = builder.category_id {
214 log::error!("buttons not implemented yet on linux");
216 }
217
218 if let Some(xdg_category) = builder.xdg_category {
219 notification.hint(Hint::Category(xdg_category.to_string()));
220 }
221
222 notification
227 .urgency(Urgency::Normal)
228 .hint(Hint::Transient(false))
229 .action("default", "default");
231 let notification_handle = notification.show_async().await?;
234
235 let user_info = builder.user_info.unwrap_or_default();
236
237 if let Some(handler) = self.handler.get() {
238 let handler_clone = handler.clone();
239 let notification_id = id.clone();
240 let cloned_user_info = user_info.clone();
241 handle_action(notification_handle.id(), move |action| {
243 let user_info = cloned_user_info.clone();
244 if let ActionResponse::Closed(reason) = action {
245 match reason {
246 CloseReason::Other(_) => {
247 log::warn!("unhandles close reason {reason:?}")
248 }
249 CloseReason::Expired | CloseReason::CloseAction => { }
250 CloseReason::Dismissed => handler_clone(NotificationResponse {
251 notification_id,
252 action: crate::NotificationResponseAction::Dismiss,
253 user_text: None,
254 user_info,
255 }),
256 }
257 } else {
258 handler_clone(NotificationResponse {
259 notification_id,
260 action: crate::NotificationResponseAction::Default,
261 user_text: None,
262 user_info,
263 });
264 }
265 });
266 } else {
267 log::error!("no handler set");
268 }
269
270 let handle = NotificationHandleXdg {
271 id,
272 user_info,
273 handle: Arc::new(RwLock::new(Some(notification_handle))),
274 };
275
276 self.add_notification(handle.clone()).await;
277 Ok(Box::new(handle) as Box<dyn NotificationHandle>)
278 }
279}