user_notify/notification.rs
1use std::{collections::HashMap, fmt::Debug, path::PathBuf};
2
3use async_trait::async_trait;
4
5use crate::{Error, xdg_category::XdgNotificationCategory};
6
7/// A builder struct for building notifications.
8#[derive(Debug, Default)]
9pub struct NotificationBuilder {
10 pub(crate) body: Option<String>,
11 pub(crate) title: Option<String>,
12 pub(crate) subtitle: Option<String>,
13 pub(crate) image: Option<std::path::PathBuf>,
14 pub(crate) icon: Option<std::path::PathBuf>,
15 pub(crate) icon_round_crop: bool,
16 pub(crate) thread_id: Option<String>,
17 pub(crate) category_id: Option<String>,
18 pub(crate) xdg_category: Option<XdgNotificationCategory>,
19 pub(crate) xdg_app_name: Option<String>,
20 pub(crate) user_info: Option<HashMap<String, String>>,
21}
22
23impl NotificationBuilder
24where
25 Self: Sized,
26{
27 /// Create a new notification builder
28 pub fn new() -> Self {
29 NotificationBuilder {
30 ..Default::default()
31 }
32 }
33 /// Main content of notification
34 ///
35 /// Plaform specific:
36 /// - MacOS: [UNNotificationContent/body](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/body)
37 /// - Linux / XDG: [body](https://specifications.freedesktop.org/notification-spec/latest/basic-design.html#:~:text=This%20is%20a%20multi,the%20summary%20is%20displayed.)
38 /// - Windows: [text2](https://docs.rs/tauri-winrt-notification/latest/tauri_winrt_notification/struct.Toast.html#method.text2)
39 pub fn body(mut self, body: &str) -> Self {
40 self.body = Some(body.to_owned());
41 self
42 }
43 /// Primary description of notification
44 ///
45 /// Plaform specific:
46 /// - MacOS: [UNNotificationContent/title](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/title)
47 /// - Linux / XDG: [summary](https://specifications.freedesktop.org/notification-spec/latest/basic-design.html#:~:text=This%20is%20a,using%20UTF%2D8.)
48 /// - Windows: [text2](https://docs.rs/tauri-winrt-notification/latest/tauri_winrt_notification/struct.Toast.html#method.text2)
49 pub fn title(mut self, title: &str) -> Self {
50 self.title = Some(title.to_owned());
51 self
52 }
53 /// Sets secondary description of Notification
54 ///
55 /// Plaform specific:
56 /// - MacOS [UNNotificationContent/subtitle](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/subtitle)
57 /// - Linux / XDG: **not suported!**
58 /// - Windows [text1](https://docs.rs/tauri-winrt-notification/latest/tauri_winrt_notification/struct.Toast.html#method.text1)
59 pub fn subtitle(mut self, subtitle: &str) -> Self {
60 self.subtitle = Some(subtitle.to_owned());
61 self
62 }
63
64 /// Set Image Attachment
65 ///
66 /// Plaform specific:
67 /// - MacOS: passed by file path, must be gif, jpg, or png
68 /// - For linux the file is read and transfered over dbus (in case you are in a flatpak and it can't read from files) ["image-data"](https://specifications.freedesktop.org/notification-spec/latest/icons-and-images.html#icons-and-images-formats)
69 /// - Windows: passed by file path. [image](https://docs.rs/tauri-winrt-notification/latest/tauri_winrt_notification/struct.Toast.html#method.image)
70 pub fn set_image(mut self, path: PathBuf) -> Self {
71 self.image = Some(path);
72 self
73 }
74
75 /// Set App icon
76 ///
77 /// Plaform specific:
78 /// - MacOS: not supported to change the app icon?
79 /// - For linux the file is read and transfered over dbus (in case you are in a flatpak and it can't read from files) [app_icon](https://specifications.freedesktop.org/notification-spec/latest/icons-and-images.html#icons-and-images-formats)
80 /// - Windows: [`<image placement="appLogoOverride" />`](https://learn.microsoft.com/uwp/schemas/tiles/toastschema/element-image)
81 pub fn set_icon(mut self, path: PathBuf) -> Self {
82 self.icon = Some(path);
83 self
84 }
85
86 /// Set App icon to be round
87 ///
88 /// Plaform specific:
89 /// - MacOS: not supported
90 /// - Linux: not supported
91 /// - Windows: [`<image placement='appLogoOverride' hint-crop='circle' />`](https://learn.microsoft.com/uwp/schemas/tiles/toastschema/element-image)
92 pub fn set_icon_round_crop(mut self, icon_round_crop: bool) -> Self {
93 self.icon_round_crop = icon_round_crop;
94 self
95 }
96
97 /// Set Thread id, this is used to group related notifications
98 ///
99 /// Plaform specific:
100 /// - MacOS: [UNNotificationContent/threadIdentifier](https://developer.apple.com/documentation/usernotifications/unnotificationcontent/threadidentifier)
101 /// - Linux not specified yet:
102 /// - Windows: not supported
103 pub fn set_thread_id(mut self, thread_id: &str) -> Self {
104 self.thread_id = Some(thread_id.to_owned());
105 self
106 }
107
108 /// Set the notification Category, those are basically templates how the notification should be displayed
109 ///
110 /// It is used to add a text field or buttons to the notification.
111 ///
112 /// Categories are defined by passing them to [NotificationManager::register] on app startup
113 pub fn set_category_id(mut self, category_id: &str) -> Self {
114 self.category_id = Some(category_id.to_owned());
115 self
116 }
117
118 /// Set the xdg notification Category
119 ///
120 /// The type of notification this is acording to <https://specifications.freedesktop.org/notification-spec/latest/categories.html>
121 ///
122 /// Platform specific: only work on linux, this does nothing on other platforms
123 pub fn set_xdg_category(mut self, category: XdgNotificationCategory) -> Self {
124 self.xdg_category = Some(category);
125 self
126 }
127
128 /// Set the xdg App Name
129 ///
130 /// Platform specific: only work on linux, this does nothing on other platforms
131 pub fn set_xdg_app_name(mut self, name: String) -> Self {
132 self.xdg_app_name = Some(name);
133 self
134 }
135
136 /// Set metadata for a notification
137 ///
138 /// ## Platform Specific
139 /// - on MacOS this uses UserInfo field in the notification content, so it works accross sessions
140 /// - windows stores this in toast [NotificationData](https://learn.microsoft.com/en-us/uwp/api/windows.ui.notifications.notificationdata?view=winrt-26100)
141 /// - linux: on linux we emulate this by storing this info inside of NotificationManager
142 pub fn set_user_info(mut self, user_info: HashMap<String, String>) -> Self {
143 self.user_info = Some(user_info);
144 self
145 }
146}
147
148/// A Handle to a sent notification
149pub trait NotificationHandle
150where
151 Self: Send + Sync + Debug,
152{
153 /// Close the notification
154 fn close(&self) -> Result<(), Error>;
155
156 /// Returns the id of the notification
157 fn get_id(&self) -> String;
158
159 /// Returns the data stored inside of the notification
160 fn get_user_info(&self) -> &HashMap<String, String>;
161}
162
163/// Manager for active notifications.
164///
165/// It is needed to display notifications and to manage active notifications.
166///
167/// ## Send a notification with a button
168/// ```rust
169/// let manager = get_notification_manager("com.example.my.app".to_string(), None);
170/// let categories = vec![
171/// NotificationCategory {
172/// identifier: "my.app.question".to_string(),
173/// actions: vec![
174/// NotificationCategoryAction::Action {
175/// identifier: "my.app.question.yes".to_string(),
176/// title: "Yes".to_string(),
177/// },
178/// NotificationCategoryAction::Action {
179/// identifier: "my.app.question.no".to_string(),
180/// title: "No".to_string(),
181/// },
182/// ],
183/// },
184/// ];
185/// manager.register(
186/// Box::new(|response| {
187/// log::info!("got notification response: {response:?}");
188/// }),
189/// categories,
190/// )?;
191/// let notification = user_notify::NotificationBuilder::new()
192/// .title("Question")
193/// .body("are you fine?")
194/// .set_category_id("my.app.question");
195/// let notification_handle = manager.send_notification(notification).await?;
196/// ```
197///
198/// Note that on macOS you need to ask for permission on first start of your app, before you can send notifications:
199/// ```rust
200/// if let Err(err) = manager
201/// .first_time_ask_for_notification_permission()
202/// .await
203/// {
204/// println!("failed to ask for notification permission: {err:?}");
205/// }
206/// ```
207#[async_trait]
208pub trait NotificationManager
209where
210 Self: Send + Sync + Debug,
211{
212 /// Returns whether the app is allowed to send notifications
213 ///
214 /// Needs to be called from **main thread**.
215 ///
216 /// ## Platform specific:
217 /// - MacOS: "Authorized", "Provisional" and "Ephemeral" return `true`.
218 /// "Denied", "NotDetermined" and unknown return `false`.
219 /// - Other: no-op on other platforms (always returns true)
220 async fn get_notification_permission_state(&self) -> Result<bool, crate::Error>;
221
222 /// Ask for notification permission.
223 ///
224 /// Needs to be called from **main thread**.
225 ///
226 /// ## Platform specific:
227 /// - MacOS: only asks the user on the first time this method is called.
228 /// - Other: no-op on other platforms (always returns true)
229 async fn first_time_ask_for_notification_permission(&self) -> Result<bool, Error>;
230
231 /// Registers and initializes the notification handler and categories.
232 /// Set a function to handle user responses (clicking notification, closing it, clicking an action on it)
233 ///
234 /// ## Platform specific:
235 /// - MacOS: sets the UNUserNotificationCenterDelegate
236 fn register(
237 &self,
238 handler_callback: Box<dyn Fn(crate::NotificationResponse) + Send + Sync + 'static>,
239 categories: Vec<NotificationCategory>,
240 ) -> Result<(), Error>;
241
242 /// Removes all of your app’s delivered notifications from Notification Center.
243 ///
244 /// ## Platform specific:
245 /// - MacOS: [UNUserNotificationCenter.removeAllDeliveredNotifications](https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/removealldeliverednotifications())
246 /// - Linux: only works for notifications from current session, because notification handles are tracked in memory
247 fn remove_all_delivered_notifications(&self) -> Result<(), Error>;
248
249 /// Removes specific delivered notifications by their id from Notification Center.
250 ///
251 /// ## Platform specific:
252 /// - Linux: only works for notifications from current session, because notification handles are tracked in memory
253 fn remove_delivered_notifications(&self, ids: Vec<&str>) -> Result<(), Error>;
254
255 /// Get all deliverd notifications from UNUserNotificationCenter that are still active.
256 ///
257 /// ## Platform specific:
258 /// - MacOS:
259 /// - also includes notifications from previous sessions
260 /// - [UNUserNotificationCenter.getDeliveredNotificationsWithCompletionHandler](https://developer.apple.com/documentation/usernotifications/unusernotificationcenter/getdeliverednotifications(completionhandler:))
261 /// - Others: TODO: implemented/emulated by keeping track of all notifications in memory
262 async fn get_active_notifications(&self) -> Result<Vec<Box<dyn NotificationHandle>>, Error>;
263
264 /// Shows notification and returns Notification handle
265 async fn send_notification(
266 &self,
267 builder: NotificationBuilder,
268 ) -> Result<Box<dyn NotificationHandle>, Error>;
269}
270
271/// Emmited when user clicked on a notification
272///
273/// ## Platform-specific
274///
275/// - **macOS**: <https://developer.apple.com/documentation/usernotifications/unusernotificationcenterdelegate/usernotificationcenter(_:didreceive:withcompletionhandler:)?language=objc>
276/// - **Other**: Unsupported.
277#[non_exhaustive]
278#[derive(Debug, Clone, PartialEq)]
279pub struct NotificationResponse {
280 /// id of the notification that was assigned by the system
281 pub notification_id: String,
282 /// The action the user took to trigger the response
283 pub action: NotificationResponseAction,
284 /// The text that the user typed in as reponse
285 ///
286 /// ## Platform Specific
287 /// - MacOS: corresponds to [UNTextInputNotificationResponse.userText](https://developer.apple.com/documentation/usernotifications/untextinputnotificationresponse/usertext?language=objc)
288 /// - Linux: not supported
289 pub user_text: Option<String>,
290 /// Data stored inside of the notification
291 pub user_info: HashMap<String, String>,
292}
293
294/// An action the user took to trigger the [NotificationResponse]
295#[derive(Debug, Clone, PartialEq)]
296pub enum NotificationResponseAction {
297 /// When user clicks on the notification
298 ///
299 /// ## Platform Specific
300 /// - MacOS: corresponds to [UNNotificationDefaultActionIdentifier](https://developer.apple.com/documentation/usernotifications/unnotificationdefaultactionidentifier?language=objc)
301 Default,
302 /// When user closes the notification
303 ///
304 /// ## Platform Specific
305 /// - MacOS: corresponds to [UNNotificationDismissActionIdentifier](https://developer.apple.com/documentation/usernotifications/unnotificationdismissactionidentifier?language=objc)
306 Dismiss,
307 /// The identifier string of the action that the user selected, if it is not one of the other actions in [NotificationResponseAction]
308 Other(String),
309}
310
311/// Notification Categories are used to define actions
312/// for notifications that have this category set.
313///
314/// Think of it like a template for notications.
315/// To store data for a notification,
316/// use [NotificationBuilder::set_user_info]
317/// and retrieve it via [NotificationHandle::get_user_info]
318/// or [NotificationResponse::user_info].
319#[derive(Debug, Clone)]
320pub struct NotificationCategory {
321 /// Id of the category by which it is referenced on notifications [NotificationBuilder::set_category_id]
322 pub identifier: String,
323 /// The actions to display when the system delivers notifications of this type.
324 pub actions: Vec<NotificationCategoryAction>,
325}
326
327/// An action to display in a notifications.
328#[derive(Debug, Clone)]
329pub enum NotificationCategoryAction {
330 /// Action button in a notification
331 /// ## Platform specific
332 /// - macOS: <https://developer.apple.com/documentation/usernotifications/unnotificationaction?language=objc>
333 /// - Linux: not implemented yet (<https://github.com/Simon-Laux/user-notify/issues/1>)
334 /// - Windows: not implemented yet (<https://github.com/Simon-Laux/user-notify/issues/2>)
335 Action {
336 /// id of the action
337 identifier: String,
338 /// Label of the button
339 title: String,
340 /* IDEA: also support icon https://developer.apple.com/documentation/usernotifications/unnotificationaction/init(identifier:title:options:icon:)?language=objc */
341 },
342 /// Text input field in a notification.
343 ///
344 /// Example Usage: Can be used to reply to notifications of a messenger.
345 ///
346 /// ## Platform specific
347 /// - macOS: <https://developer.apple.com/documentation/usernotifications/untextinputnotificationaction>
348 /// - Linux: not supported
349 /// - Windows: not implemented yet (<https://github.com/Simon-Laux/user-notify/issues/2>)
350 TextInputAction {
351 /// id of the action
352 identifier: String,
353 /// Label of the input field
354 title: String,
355 /* IDEA: also support icon and option https://developer.apple.com/documentation/usernotifications/untextinputnotificationaction/init(identifier:title:options:textinputbuttontitle:textinputplaceholder:)?language=objc */
356 /// Label of the input button
357 input_button_title: String,
358 /// Placeholder for the input field
359 input_placeholder: String,
360 },
361}