Skip to main content

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}