Skip to main content

tauri_plugin_notifications/
desktop.rs

1use serde::de::DeserializeOwned;
2use tauri::{
3    plugin::{PermissionState, PluginApi},
4    AppHandle, Runtime,
5};
6
7use crate::NotificationsBuilder;
8
9// Signature must match the iOS/Android `init` so the cfg-gated call sites in `lib.rs::init` compile uniformly.
10#[allow(clippy::unnecessary_wraps)]
11pub fn init<R: Runtime, C: DeserializeOwned>(
12    app: &AppHandle<R>,
13    _api: PluginApi<R, C>,
14) -> crate::Result<Notifications<R>> {
15    Ok(Notifications(app.clone()))
16}
17
18/// Access to the notification APIs.
19///
20/// You can get an instance of this type via [`NotificationsExt`](crate::NotificationsExt)
21pub struct Notifications<R: Runtime>(AppHandle<R>);
22
23// `async` and `Result` mirror the mobile/macOS plugin API so callers can `.await` and `?` uniformly.
24#[allow(clippy::unused_async, clippy::unnecessary_wraps)]
25impl<R: Runtime> crate::NotificationsBuilder<R> {
26    pub async 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
43        notification.show()?;
44
45        Ok(())
46    }
47}
48
49// `async` mirrors the mobile/macOS plugin API so callers can `.await` uniformly.
50#[allow(clippy::unused_async)]
51impl<R: Runtime> Notifications<R> {
52    pub fn builder(&self) -> NotificationsBuilder<R> {
53        NotificationsBuilder::new(self.0.clone())
54    }
55
56    pub async fn request_permission(&self) -> crate::Result<PermissionState> {
57        Ok(PermissionState::Granted)
58    }
59
60    pub async fn register_for_push_notifications(&self) -> crate::Result<String> {
61        Err(crate::Error::Io(std::io::Error::other(
62            "Push notifications are not supported on desktop platforms",
63        )))
64    }
65
66    pub fn unregister_for_push_notifications(&self) -> crate::Result<()> {
67        Err(crate::Error::Io(std::io::Error::other(
68            "Push notifications are not supported on desktop platforms",
69        )))
70    }
71
72    pub async fn permission_state(&self) -> crate::Result<PermissionState> {
73        Ok(PermissionState::Granted)
74    }
75
76    pub async fn pending(&self) -> crate::Result<Vec<crate::PendingNotification>> {
77        Err(crate::Error::Io(std::io::Error::other(
78            "Pending notifications are not supported with notify-rust",
79        )))
80    }
81
82    pub async fn active(&self) -> crate::Result<Vec<crate::ActiveNotification>> {
83        Err(crate::Error::Io(std::io::Error::other(
84            "Active notifications are not supported with notify-rust",
85        )))
86    }
87
88    pub fn set_click_listener_active(&self, _active: bool) -> crate::Result<()> {
89        Err(crate::Error::Io(std::io::Error::other(
90            "Click listeners are not supported with notify-rust",
91        )))
92    }
93
94    pub fn remove_active(&self, _ids: Vec<i32>) -> crate::Result<()> {
95        Err(crate::Error::Io(std::io::Error::other(
96            "Removing active notifications is not supported with notify-rust",
97        )))
98    }
99
100    pub fn cancel(&self, _notifications: Vec<i32>) -> crate::Result<()> {
101        Err(crate::Error::Io(std::io::Error::other(
102            "Canceling notifications is not supported with notify-rust",
103        )))
104    }
105
106    pub fn cancel_all(&self) -> crate::Result<()> {
107        Err(crate::Error::Io(std::io::Error::other(
108            "Canceling notifications is not supported with notify-rust",
109        )))
110    }
111
112    pub fn register_action_types(&self, _types: Vec<crate::ActionType>) -> crate::Result<()> {
113        Err(crate::Error::Io(std::io::Error::other(
114            "Action types are not supported with notify-rust",
115        )))
116    }
117
118    pub fn create_channel(&self, _channel: crate::Channel) -> crate::Result<()> {
119        Err(crate::Error::Io(std::io::Error::other(
120            "Notification channels are not supported with notify-rust",
121        )))
122    }
123
124    pub fn delete_channel(&self, _id: impl Into<String>) -> crate::Result<()> {
125        Err(crate::Error::Io(std::io::Error::other(
126            "Notification channels are not supported with notify-rust",
127        )))
128    }
129
130    pub fn list_channels(&self) -> crate::Result<Vec<crate::Channel>> {
131        Err(crate::Error::Io(std::io::Error::other(
132            "Notification channels are not supported with notify-rust",
133        )))
134    }
135}
136
137mod imp {
138    //! Types and functions related to desktop notifications.
139
140    #[cfg(windows)]
141    use std::path::MAIN_SEPARATOR as SEP;
142
143    /// The desktop notification definition.
144    ///
145    /// Allows you to construct a Notification data and send it.
146    #[allow(dead_code)]
147    #[derive(Debug, Default)]
148    pub struct Notification {
149        /// The notification body.
150        body: Option<String>,
151        /// The notification title.
152        title: Option<String>,
153        /// The notification icon.
154        icon: Option<String>,
155        /// The notification identifier
156        identifier: String,
157    }
158
159    impl Notification {
160        /// Initializes a instance of a Notification.
161        pub fn new(identifier: impl Into<String>) -> Self {
162            Self {
163                identifier: identifier.into(),
164                ..Default::default()
165            }
166        }
167
168        /// Sets the notification body.
169        #[must_use]
170        pub fn body(mut self, body: impl Into<String>) -> Self {
171            self.body = Some(body.into());
172            self
173        }
174
175        /// Sets the notification title.
176        #[must_use]
177        pub fn title(mut self, title: impl Into<String>) -> Self {
178            self.title = Some(title.into());
179            self
180        }
181
182        /// Sets the notification icon.
183        #[must_use]
184        pub fn icon(mut self, icon: impl Into<String>) -> Self {
185            self.icon = Some(icon.into());
186            self
187        }
188
189        /// Shows the notification.
190        // `current_exe()?` returns Result on Windows; Result is kept for that branch.
191        #[allow(clippy::unnecessary_wraps)]
192        pub fn show(self) -> crate::Result<()> {
193            let mut notification = notify_rust::Notification::new();
194            if let Some(body) = self.body {
195                notification.body(&body);
196            }
197            if let Some(title) = self.title {
198                notification.summary(&title);
199            }
200            if let Some(icon) = self.icon {
201                notification.icon(&icon);
202            } else {
203                notification.auto_icon();
204            }
205            #[cfg(windows)]
206            {
207                let exe = tauri::utils::platform::current_exe()?;
208                let exe_dir = exe.parent().expect("failed to get exe directory");
209                let curr_dir = exe_dir.display().to_string();
210                // set the notification's System.AppUserModel.ID only when running the installed app
211                if !(curr_dir.ends_with(format!("{SEP}target{SEP}debug").as_str())
212                    || curr_dir.ends_with(format!("{SEP}target{SEP}release").as_str()))
213                {
214                    notification.app_id(&self.identifier);
215                }
216            }
217            #[cfg(target_os = "macos")]
218            {
219                let _ = notify_rust::set_application(if tauri::is_dev() {
220                    "com.apple.Terminal"
221                } else {
222                    &self.identifier
223                });
224            }
225
226            tauri::async_runtime::spawn(async move {
227                let _ = notification.show();
228            });
229
230            Ok(())
231        }
232    }
233}