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    plugin::{PermissionState, PluginApi},
8    AppHandle, Runtime,
9};
10
11use crate::NotificationBuilder;
12
13pub fn init<R: Runtime, C: DeserializeOwned>(
14    app: &AppHandle<R>,
15    _api: PluginApi<R, C>,
16) -> crate::Result<Notification<R>> {
17    Ok(Notification(app.clone()))
18}
19
20/// Access to the notification APIs.
21///
22/// You can get an instance of this type via [`NotificationExt`](crate::NotificationExt)
23pub struct Notification<R: Runtime>(AppHandle<R>);
24
25impl<R: Runtime> crate::NotificationBuilder<R> {
26    pub fn show(self) -> crate::Result<()> {
27        let mut notification = imp::Notification::new(self.app.config().identifier.clone());
28
29        if let Some(title) = self
30            .data
31            .title
32            .or_else(|| self.app.config().product_name.clone())
33        {
34            notification = notification.title(title);
35        }
36        if let Some(body) = self.data.body {
37            notification = notification.body(body);
38        }
39        if let Some(icon) = self.data.icon {
40            notification = notification.icon(icon);
41        }
42        if let Some(sound) = self.data.sound {
43            notification = notification.sound(sound);
44        }
45        #[cfg(feature = "windows7-compat")]
46        {
47            notification.notify(&self.app)?;
48        }
49        #[cfg(not(feature = "windows7-compat"))]
50        notification.show()?;
51
52        Ok(())
53    }
54}
55
56impl<R: Runtime> Notification<R> {
57    pub fn builder(&self) -> NotificationBuilder<R> {
58        NotificationBuilder::new(self.0.clone())
59    }
60
61    pub fn request_permission(&self) -> crate::Result<PermissionState> {
62        Ok(PermissionState::Granted)
63    }
64
65    pub fn permission_state(&self) -> crate::Result<PermissionState> {
66        Ok(PermissionState::Granted)
67    }
68}
69
70mod imp {
71    //! Types and functions related to desktop notifications.
72
73    #[cfg(windows)]
74    use std::path::MAIN_SEPARATOR as SEP;
75
76    /// The desktop notification definition.
77    ///
78    /// Allows you to construct a Notification data and send it.
79    ///
80    /// # Examples
81    /// ```rust,no_run
82    /// use tauri_plugin_notification::NotificationExt;
83    /// // first we build the application to access the Tauri configuration
84    /// let app = tauri::Builder::default()
85    ///   // on an actual app, remove the string argument
86    ///   .build(tauri::generate_context!("test/tauri.conf.json"))
87    ///   .expect("error while building tauri application");
88    ///
89    /// // shows a notification with the given title and body
90    /// app.notification()
91    ///   .builder()
92    ///   .title("New message")
93    ///   .body("You've got a new message.")
94    ///   .show();
95    ///
96    /// // run the app
97    /// app.run(|_app_handle, _event| {});
98    /// ```
99    #[allow(dead_code)]
100    #[derive(Debug, Default)]
101    pub struct Notification {
102        /// The notification body.
103        body: Option<String>,
104        /// The notification title.
105        title: Option<String>,
106        /// The notification icon.
107        icon: Option<String>,
108        /// The notification sound.
109        sound: Option<String>,
110        /// The notification identifier
111        identifier: String,
112    }
113
114    impl Notification {
115        /// Initializes a instance of a Notification.
116        pub fn new(identifier: impl Into<String>) -> Self {
117            Self {
118                identifier: identifier.into(),
119                ..Default::default()
120            }
121        }
122
123        /// Sets the notification body.
124        #[must_use]
125        pub fn body(mut self, body: impl Into<String>) -> Self {
126            self.body = Some(body.into());
127            self
128        }
129
130        /// Sets the notification title.
131        #[must_use]
132        pub fn title(mut self, title: impl Into<String>) -> Self {
133            self.title = Some(title.into());
134            self
135        }
136
137        /// Sets the notification icon.
138        #[must_use]
139        pub fn icon(mut self, icon: impl Into<String>) -> Self {
140            self.icon = Some(icon.into());
141            self
142        }
143
144        /// Sets the notification sound file.
145        #[must_use]
146        pub fn sound(mut self, sound: impl Into<String>) -> Self {
147            self.sound = Some(sound.into());
148            self
149        }
150
151        /// Shows the notification.
152        ///
153        /// # Examples
154        ///
155        /// ```no_run
156        /// use tauri_plugin_notification::NotificationExt;
157        ///
158        /// tauri::Builder::default()
159        ///   .setup(|app| {
160        ///     app.notification()
161        ///       .builder()
162        ///       .title("Tauri")
163        ///       .body("Tauri is awesome!")
164        ///       .show()
165        ///       .unwrap();
166        ///     Ok(())
167        ///   })
168        ///   .run(tauri::generate_context!("test/tauri.conf.json"))
169        ///   .expect("error while running tauri application");
170        /// ```
171        ///
172        /// ## Platform-specific
173        ///
174        /// - **Windows**: Not supported on Windows 7. If your app targets it, enable the `windows7-compat` feature and use [`Self::notify`].
175        #[cfg_attr(
176            all(not(docsrs), feature = "windows7-compat"),
177            deprecated = "This function does not work on Windows 7. Use `Self::notify` instead."
178        )]
179        pub fn show(self) -> crate::Result<()> {
180            let mut notification = notify_rust::Notification::new();
181            if let Some(body) = self.body {
182                notification.body(&body);
183            }
184            if let Some(title) = self.title {
185                notification.summary(&title);
186            }
187            if let Some(icon) = self.icon {
188                notification.icon(&icon);
189            } else {
190                notification.auto_icon();
191            }
192            if let Some(sound) = self.sound {
193                notification.sound_name(&sound);
194            }
195            #[cfg(windows)]
196            {
197                let exe = tauri::utils::platform::current_exe()?;
198                let exe_dir = exe.parent().expect("failed to get exe directory");
199                let curr_dir = exe_dir.display().to_string();
200                // set the notification's System.AppUserModel.ID only when running the installed app
201                if !(curr_dir.ends_with(format!("{SEP}target{SEP}debug").as_str())
202                    || curr_dir.ends_with(format!("{SEP}target{SEP}release").as_str()))
203                {
204                    notification.app_id(&self.identifier);
205                }
206            }
207            #[cfg(target_os = "macos")]
208            {
209                let _ = notify_rust::set_application(if tauri::is_dev() {
210                    "com.apple.Terminal"
211                } else {
212                    &self.identifier
213                });
214            }
215
216            tauri::async_runtime::spawn(async move {
217                let _ = notification.show();
218            });
219
220            Ok(())
221        }
222
223        /// Shows the notification. This API is similar to [`Self::show`], but it also works on Windows 7.
224        ///
225        /// # Examples
226        ///
227        /// ```no_run
228        /// use tauri_plugin_notification::NotificationExt;
229        ///
230        /// tauri::Builder::default()
231        ///   .setup(move |app| {
232        ///     app.notification().builder()
233        ///       .title("Tauri")
234        ///       .body("Tauri is awesome!")
235        ///       .show()
236        ///       .unwrap();
237        ///     Ok(())
238        ///   })
239        ///   .run(tauri::generate_context!("test/tauri.conf.json"))
240        ///   .expect("error while running tauri application");
241        /// ```
242        #[cfg(feature = "windows7-compat")]
243        #[cfg_attr(docsrs, doc(cfg(feature = "windows7-compat")))]
244        #[allow(unused_variables)]
245        pub fn notify<R: tauri::Runtime>(self, app: &tauri::AppHandle<R>) -> crate::Result<()> {
246            #[cfg(windows)]
247            {
248                fn is_windows_7() -> bool {
249                    let v = windows_version::OsVersion::current();
250                    // windows 7 is 6.1
251                    v.major == 6 && v.minor == 1
252                }
253
254                if is_windows_7() {
255                    self.notify_win7(app)
256                } else {
257                    #[allow(deprecated)]
258                    self.show()
259                }
260            }
261            #[cfg(not(windows))]
262            {
263                #[allow(deprecated)]
264                self.show()
265            }
266        }
267
268        /// Shows the notification on Windows 7.
269        #[cfg(all(windows, feature = "windows7-compat"))]
270        fn notify_win7<R: tauri::Runtime>(self, app: &tauri::AppHandle<R>) -> crate::Result<()> {
271            let app_ = app.clone();
272            let _ = app.clone().run_on_main_thread(move || {
273                let mut notification = win7_notifications::Notification::new();
274                if let Some(body) = self.body {
275                    notification.body(&body);
276                }
277                if let Some(title) = self.title {
278                    notification.summary(&title);
279                }
280                if let Some(icon) = app_.default_window_icon() {
281                    notification.icon(icon.rgba().to_vec(), icon.width(), icon.height());
282                }
283                let _ = notification.show();
284            });
285
286            Ok(())
287        }
288    }
289}