tauri_plugin_notification/desktop.rs
1// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
2// SPDX-License-Identifier: Apache-2.0
3// SPDX-License-Identifier: MIT
4
5use serde::de::DeserializeOwned;
6use tauri::{
7 AppHandle, Runtime,
8 plugin::{PermissionState, PluginApi},
9};
10
11use crate::NotificationBuilder;
12
13/// Initializes the desktop implementation of the notification APIs.
14pub fn init<R: Runtime, C: DeserializeOwned>(
15 app: &AppHandle<R>,
16 _api: PluginApi<R, C>,
17) -> crate::Result<Notification<R>> {
18 Ok(Notification(app.clone()))
19}
20
21/// Access to the notification APIs.
22///
23/// You can get an instance of this type via [`NotificationExt`](crate::NotificationExt)
24pub struct Notification<R: Runtime>(AppHandle<R>);
25
26impl<R: Runtime> crate::NotificationBuilder<R> {
27 /// Shows the notification.
28 ///
29 /// When no title was set with [`Self::title`], the `productName` from the Tauri configuration is used instead.
30 /// Only the title, body, icon and sound of the notification are used on desktop;
31 /// the scheduling, grouping and action related options are ignored.
32 ///
33 /// The notification is dispatched on a background task, so this returns as soon as the payload is prepared.
34 ///
35 /// # Errors
36 ///
37 /// Returns an error when the notification could not be prepared,
38 /// e.g. when the path of the running executable cannot be resolved on Windows.
39 pub fn show(self) -> crate::Result<()> {
40 let mut notification = imp::Notification::new(self.app.config().identifier.clone());
41
42 if let Some(title) = self
43 .data
44 .title
45 .or_else(|| self.app.config().product_name.clone())
46 {
47 notification = notification.title(title);
48 }
49 if let Some(body) = self.data.body {
50 notification = notification.body(body);
51 }
52 if let Some(icon) = self.data.icon {
53 notification = notification.icon(icon);
54 }
55 if let Some(sound) = self.data.sound {
56 notification = notification.sound(sound);
57 }
58 notification.show()?;
59
60 Ok(())
61 }
62}
63
64impl<R: Runtime> Notification<R> {
65 /// Creates a new builder for a notification.
66 ///
67 /// # Examples
68 ///
69 /// ```no_run
70 /// use tauri_plugin_notification::NotificationExt;
71 ///
72 /// fn notify<R: tauri::Runtime>(app: &tauri::AppHandle<R>) {
73 /// app.notification()
74 /// .builder()
75 /// .title("Tauri")
76 /// .body("Tauri is awesome!")
77 /// .show()
78 /// .unwrap();
79 /// }
80 /// ```
81 pub fn builder(&self) -> NotificationBuilder<R> {
82 NotificationBuilder::new(self.0.clone())
83 }
84
85 /// Requests the permission to send notifications.
86 ///
87 /// Desktop applications do not need to ask for this permission,
88 /// so this always resolves to [`PermissionState::Granted`] without prompting the user.
89 pub fn request_permission(&self) -> crate::Result<PermissionState> {
90 Ok(PermissionState::Granted)
91 }
92
93 /// Checks whether the permission to send notifications was granted.
94 ///
95 /// Desktop applications do not need to ask for this permission,
96 /// so this always resolves to [`PermissionState::Granted`].
97 pub fn permission_state(&self) -> crate::Result<PermissionState> {
98 Ok(PermissionState::Granted)
99 }
100}
101
102mod imp {
103 //! Types and functions related to desktop notifications.
104
105 #[cfg(windows)]
106 use std::path::MAIN_SEPARATOR as SEP;
107
108 /// The desktop notification definition.
109 ///
110 /// Allows you to construct a Notification data and send it.
111 ///
112 /// # Examples
113 /// ```rust,no_run
114 /// use tauri_plugin_notification::NotificationExt;
115 /// // first we build the application to access the Tauri configuration
116 /// let app = tauri::Builder::default()
117 /// // on an actual app, remove the string argument
118 /// .build(tauri::generate_context!("test/tauri.conf.json"))
119 /// .expect("error while building tauri application");
120 ///
121 /// // shows a notification with the given title and body
122 /// app.notification()
123 /// .builder()
124 /// .title("New message")
125 /// .body("You've got a new message.")
126 /// .show();
127 ///
128 /// // run the app
129 /// app.run(|_app_handle, _event| {});
130 /// ```
131 #[allow(dead_code)]
132 #[derive(Debug, Default)]
133 pub struct Notification {
134 /// The notification body.
135 body: Option<String>,
136 /// The notification title.
137 title: Option<String>,
138 /// The notification icon.
139 icon: Option<String>,
140 /// The notification sound.
141 sound: Option<String>,
142 /// The notification identifier
143 identifier: String,
144 }
145
146 impl Notification {
147 /// Initializes a instance of a Notification.
148 pub fn new(identifier: impl Into<String>) -> Self {
149 Self {
150 identifier: identifier.into(),
151 ..Default::default()
152 }
153 }
154
155 /// Sets the notification body.
156 #[must_use]
157 pub fn body(mut self, body: impl Into<String>) -> Self {
158 self.body = Some(body.into());
159 self
160 }
161
162 /// Sets the notification title.
163 #[must_use]
164 pub fn title(mut self, title: impl Into<String>) -> Self {
165 self.title = Some(title.into());
166 self
167 }
168
169 /// Sets the notification icon.
170 #[must_use]
171 pub fn icon(mut self, icon: impl Into<String>) -> Self {
172 self.icon = Some(icon.into());
173 self
174 }
175
176 /// Sets the notification sound file.
177 #[must_use]
178 pub fn sound(mut self, sound: impl Into<String>) -> Self {
179 self.sound = Some(sound.into());
180 self
181 }
182
183 /// Shows the notification.
184 ///
185 /// # Examples
186 ///
187 /// ```no_run
188 /// use tauri_plugin_notification::NotificationExt;
189 ///
190 /// tauri::Builder::default()
191 /// .setup(|app| {
192 /// app.notification()
193 /// .builder()
194 /// .title("Tauri")
195 /// .body("Tauri is awesome!")
196 /// .show()
197 /// .unwrap();
198 /// Ok(())
199 /// })
200 /// .run(tauri::generate_context!("test/tauri.conf.json"))
201 /// .expect("error while running tauri application");
202 /// ```
203 pub fn show(self) -> crate::Result<()> {
204 let mut notification = notify_rust::Notification::new();
205 if let Some(body) = self.body {
206 notification.body(&body);
207 }
208 if let Some(title) = self.title {
209 notification.summary(&title);
210 }
211 if let Some(icon) = self.icon {
212 notification.icon(&icon);
213 } else {
214 notification.auto_icon();
215 }
216 if let Some(sound) = self.sound {
217 notification.sound_name(&sound);
218 }
219 #[cfg(windows)]
220 {
221 let exe = tauri::utils::platform::current_exe()?;
222 let exe_dir = exe.parent().expect("failed to get exe directory");
223 let curr_dir = exe_dir.display().to_string();
224 // set the notification's System.AppUserModel.ID only when running the installed app
225 if !(curr_dir.ends_with(format!("{SEP}target{SEP}debug").as_str())
226 || curr_dir.ends_with(format!("{SEP}target{SEP}release").as_str()))
227 {
228 notification.app_id(&self.identifier);
229 }
230 }
231 #[cfg(target_os = "macos")]
232 {
233 let _ = notify_rust::set_application(if tauri::is_dev() {
234 "com.apple.Terminal"
235 } else {
236 &self.identifier
237 });
238 }
239
240 tauri::async_runtime::spawn(async move {
241 let _ = notification.show();
242 });
243
244 Ok(())
245 }
246
247 /// Shows the notification. Same as [`Self::show`].
248 #[cfg(feature = "windows7-compat")]
249 #[allow(dead_code)]
250 #[cfg_attr(docsrs, doc(cfg(feature = "windows7-compat")))]
251 #[deprecated = "Tauri no longer supports Windows 7, use `Self::show` instead."]
252 pub fn notify<R: tauri::Runtime>(self, _app: &tauri::AppHandle<R>) -> crate::Result<()> {
253 self.show()
254 }
255 }
256}