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}