1 //! This module contains `XDG` and `DBus` specific code.
2 //!
3 //! it should not be available under any platform other than `(unix, not(target_os = "macos"))`
4 
5 #[cfg(feature = "dbus")]
6 use dbus::ffidisp::Connection as DbusConnection;
7 
8 use crate::{error::*, notification::Notification};
9 
10 use std::ops::{Deref, DerefMut};
11 
12 #[cfg(feature = "dbus")]
13 mod dbus_rs;
14 #[cfg(feature = "zbus")]
15 mod zbus_rs;
16 
17 #[cfg(not(feature = "debug_namespace"))]
18 pub static NOTIFICATION_NAMESPACE: &str = "org.freedesktop.Notifications";
19 #[cfg(not(feature = "debug_namespace"))]
20 pub static NOTIFICATION_OBJECTPATH: &str = "/org/freedesktop/Notifications";
21 
22 #[cfg(feature = "debug_namespace")]
23 pub static NOTIFICATION_NAMESPACE: &str = "de.hoodie.Notifications";
24 #[cfg(feature = "debug_namespace")]
25 pub static NOTIFICATION_OBJECTPATH: &str = "/de/hoodie/Notifications";
26 
27 #[derive(Debug)]
28 enum NotificationHandleInner {
29     #[cfg(feature = "dbus")]
30     Dbus(dbus_rs::DbusNotificationHandle),
31 
32     #[cfg(feature = "zbus")]
33     Zbus(zbus_rs::ZbusNotificationHandle),
34 }
35 
36 /// A handle to a shown notification.
37 ///
38 /// This keeps a connection alive to ensure actions work on certain desktops.
39 #[derive(Debug)]
40 pub struct NotificationHandle {
41     inner: NotificationHandleInner,
42 }
43 
44 #[allow(dead_code)]
45 impl NotificationHandle {
46     #[cfg(feature = "dbus")]
for_dbus(id: u32, connection: DbusConnection, notification: Notification) -> NotificationHandle47     pub(crate) fn for_dbus(id: u32, connection: DbusConnection, notification: Notification) -> NotificationHandle {
48         NotificationHandle {
49             inner: dbus_rs::DbusNotificationHandle::new(id, connection, notification).into(),
50         }
51     }
52 
53     #[cfg(feature = "zbus")]
for_zbus(id: u32, connection: zbus::Connection, notification: Notification) -> NotificationHandle54     pub(crate) fn for_zbus(id: u32, connection: zbus::Connection, notification: Notification) -> NotificationHandle {
55         NotificationHandle {
56             inner: zbus_rs::ZbusNotificationHandle::new(id, connection, notification).into(),
57         }
58     }
59 
60     /// Waits for the user to act on a notification and then calls
61     /// `invocation_closure` with the name of the corresponding action.
wait_for_action<F>(self, invocation_closure: F) where F: FnOnce(&str),62     pub fn wait_for_action<F>(self, invocation_closure: F)
63     where
64         F: FnOnce(&str),
65     {
66         match self.inner {
67             #[cfg(feature = "dbus")]
68             NotificationHandleInner::Dbus(inner) => inner.wait_for_action(|action: &ActionResponse| match action {
69                 ActionResponse::Custom(action) => invocation_closure(action),
70                 ActionResponse::Closed(_reason) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
71             }),
72 
73             #[cfg(feature = "zbus")]
74             NotificationHandleInner::Zbus(inner) => inner.wait_for_action(|action: &ActionResponse| match action {
75                 ActionResponse::Custom(action) => invocation_closure(action),
76                 ActionResponse::Closed(_reason) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
77             }),
78         };
79     }
80 
81     /// Manually close the notification
82     ///
83     /// # Example
84     ///
85     /// ```no_run
86     /// # use notify_rust::*;
87     /// let handle: NotificationHandle = Notification::new()
88     ///     .summary("oh no")
89     ///     .hint(notify_rust::Hint::Transient(true))
90     ///     .body("I'll be here till you close me!")
91     ///     .hint(Hint::Resident(true)) // does not work on kde
92     ///     .timeout(Timeout::Never) // works on kde and gnome
93     ///     .show()
94     ///     .unwrap();
95     /// // ... and then later
96     /// handle.close();
97     /// ```
close(self)98     pub fn close(self) {
99         match self.inner {
100             #[cfg(feature = "dbus")]
101             NotificationHandleInner::Dbus(inner) => inner.close(),
102             #[cfg(feature = "zbus")]
103             NotificationHandleInner::Zbus(inner) => inner.close(),
104         }
105     }
106 
107     /// Executes a closure after the notification has closed.
108     ///
109     /// ## Example 1: *I don't care about why it closed* (the good ole API)
110     ///
111     /// ```no_run
112     /// # use notify_rust::Notification;
113     /// Notification::new().summary("Time is running out")
114     ///                    .body("This will go away.")
115     ///                    .icon("clock")
116     ///                    .show()
117     ///                    .unwrap()
118     ///                    .on_close(|| println!("closed"));
119     /// ```
120     ///
121     /// ## Example 2: *I **do** care about why it closed* (added in v4.5.0)
122     ///
123     /// ```no_run
124     /// # use notify_rust::Notification;
125     /// Notification::new().summary("Time is running out")
126     ///                    .body("This will go away.")
127     ///                    .icon("clock")
128     ///                    .show()
129     ///                    .unwrap()
130     ///                    .on_close(|reason| println!("closed: {:?}", reason));
131     /// ```
on_close<A>(self, handler: impl CloseHandler<A>)132     pub fn on_close<A>(self, handler: impl CloseHandler<A>) {
133         match self.inner {
134             #[cfg(feature = "dbus")]
135             NotificationHandleInner::Dbus(inner) => inner.wait_for_action(|action: &ActionResponse| {
136                 if let ActionResponse::Closed(reason) = action {
137                     handler.call(*reason);
138                 }
139             }),
140             #[cfg(feature = "zbus")]
141             NotificationHandleInner::Zbus(inner) => inner.wait_for_action(|action: &ActionResponse| {
142                 if let ActionResponse::Closed(reason) = action {
143                     handler.call(*reason);
144                 }
145             }),
146         };
147     }
148 
149     /// Replace the original notification with an updated version
150     /// ## Example
151     /// ```no_run
152     /// # use notify_rust::Notification;
153     /// let mut notification = Notification::new().summary("Latest News")
154     ///                                           .body("Bayern Dortmund 3:2")
155     ///                                           .show()
156     ///                                           .unwrap();
157     ///
158     /// std::thread::sleep_ms(1_500);
159     ///
160     /// notification.summary("Latest News (Correction)")
161     ///             .body("Bayern Dortmund 3:3");
162     ///
163     /// notification.update();
164     /// ```
165     /// Watch out for different implementations of the
166     /// notification server! On plasma5 for instance, you should also change the appname, so the old
167     /// message is really replaced and not just amended. Xfce behaves well, all others have not
168     /// been tested by the developer.
update(&mut self)169     pub fn update(&mut self) {
170         match self.inner {
171             #[cfg(feature = "dbus")]
172             NotificationHandleInner::Dbus(ref mut inner) => inner.update(),
173             #[cfg(feature = "zbus")]
174             NotificationHandleInner::Zbus(ref mut inner) => inner.update(),
175         }
176     }
177 
178     /// Returns the Handle's id.
id(&self) -> u32179     pub fn id(&self) -> u32 {
180         match self.inner {
181             #[cfg(feature = "dbus")]
182             NotificationHandleInner::Dbus(ref inner) => inner.id,
183             #[cfg(feature = "zbus")]
184             NotificationHandleInner::Zbus(ref inner) => inner.id,
185         }
186     }
187 }
188 
189 /// Required for `DerefMut`
190 impl Deref for NotificationHandle {
191     type Target = Notification;
192 
deref(&self) -> &Notification193     fn deref(&self) -> &Notification {
194         match self.inner {
195             #[cfg(feature = "dbus")]
196             NotificationHandleInner::Dbus(ref inner) => &inner.notification,
197             #[cfg(feature = "zbus")]
198             NotificationHandleInner::Zbus(ref inner) => &inner.notification,
199         }
200     }
201 }
202 
203 /// Allow you to easily modify notification properties
204 impl DerefMut for NotificationHandle {
deref_mut(&mut self) -> &mut Notification205     fn deref_mut(&mut self) -> &mut Notification {
206         match self.inner {
207             #[cfg(feature = "dbus")]
208             NotificationHandleInner::Dbus(ref mut inner) => &mut inner.notification,
209             #[cfg(feature = "zbus")]
210             NotificationHandleInner::Zbus(ref mut inner) => &mut inner.notification,
211         }
212     }
213 }
214 
215 #[cfg(feature = "dbus")]
216 impl From<dbus_rs::DbusNotificationHandle> for NotificationHandleInner {
from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner217     fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner {
218         NotificationHandleInner::Dbus(handle)
219     }
220 }
221 
222 #[cfg(feature = "zbus")]
223 impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandleInner {
from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner224     fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner {
225         NotificationHandleInner::Zbus(handle)
226     }
227 }
228 
229 #[cfg(feature = "dbus")]
230 impl From<dbus_rs::DbusNotificationHandle> for NotificationHandle {
from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle231     fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle {
232         NotificationHandle { inner: handle.into() }
233     }
234 }
235 
236 #[cfg(feature = "zbus")]
237 impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandle {
from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandle238     fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandle {
239         NotificationHandle { inner: handle.into() }
240     }
241 }
242 
243 // here be public functions
244 
245 #[cfg(all(not(any(feature = "dbus", feature = "zbus")), unix, not(target_os = "macos")))]
246 compile_error!("you have to build with eiter zbus or dbus turned on");
247 
248 /// Which Dbus implementation are we using?
249 #[derive(Copy, Clone, Debug)]
250 pub enum DbusStack {
251     /// using [dbus-rs](https://docs.rs/dbus-rs)
252     Dbus,
253     /// using [zbus](https://docs.rs/zbus)
254     Zbus,
255 }
256 
257 #[cfg(all(feature = "dbus", feature = "zbus"))]
258 const DBUS_SWITCH_VAR: &str = "DBUSRS";
259 
260 #[cfg(all(feature = "zbus", not(feature = "dbus")))]
show_notification(notification: &Notification) -> Result<NotificationHandle>261 pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
262     zbus_rs::connect_and_send_notification(notification).map(Into::into)
263 }
264 
265 #[cfg(all(feature = "dbus", not(feature = "zbus")))]
show_notification(notification: &Notification) -> Result<NotificationHandle>266 pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
267     dbus_rs::connect_and_send_notification(notification).map(Into::into)
268 }
269 
270 #[cfg(all(feature = "dbus", feature = "zbus"))]
show_notification(notification: &Notification) -> Result<NotificationHandle>271 pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
272     if std::env::var(DBUS_SWITCH_VAR).is_ok() {
273         dbus_rs::connect_and_send_notification(notification).map(Into::into)
274     } else {
275         zbus_rs::connect_and_send_notification(notification).map(Into::into)
276     }
277 }
278 
279 /// Get the currently dsed [`DbusStack`]
280 ///
281 /// (zbus only)
282 #[cfg(all(feature = "zbus", not(feature = "dbus")))]
dbus_stack() -> Option<DbusStack>283 pub fn dbus_stack() -> Option<DbusStack> {
284     Some(DbusStack::Zbus)
285 }
286 
287 /// Get the currently dsed [`DbusStack`]
288 ///
289 /// (dbus-rs only)
290 #[cfg(all(feature = "dbus", not(feature = "zbus")))]
dbus_stack() -> Option<DbusStack>291 pub fn dbus_stack() -> Option<DbusStack> {
292     Some(DbusStack::Dbus)
293 }
294 
295 /// Get the currently dsed [`DbusStack`]
296 ///
297 /// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
298 #[cfg(all(feature = "dbus", feature = "zbus"))]
dbus_stack() -> Option<DbusStack>299 pub fn dbus_stack() -> Option<DbusStack> {
300     Some(if std::env::var(DBUS_SWITCH_VAR).is_ok() {
301         DbusStack::Dbus
302     } else {
303         DbusStack::Zbus
304     })
305 }
306 
307 /// Get the currently dsed [`DbusStack`]
308 ///
309 /// neither zbus nor dbus-rs are configured
310 #[cfg(all(not(feature = "dbus"), not(feature = "zbus")))]
dbus_stack() -> Option<DbusStack>311 pub fn dbus_stack() -> Option<DbusStack> {
312     None
313 }
314 
315 /// Get list of all capabilities of the running notification server.
316 ///
317 /// (zbus only)
318 #[cfg(all(feature = "zbus", not(feature = "dbus")))]
get_capabilities() -> Result<Vec<String>>319 pub fn get_capabilities() -> Result<Vec<String>> {
320     zbus_rs::get_capabilities()
321 }
322 
323 /// Get list of all capabilities of the running notification server.
324 ///
325 /// (dbus-rs only)
326 #[cfg(all(feature = "dbus", not(feature = "zbus")))]
get_capabilities() -> Result<Vec<String>>327 pub fn get_capabilities() -> Result<Vec<String>> {
328     dbus_rs::get_capabilities()
329 }
330 
331 /// Get list of all capabilities of the running notification server.
332 ///
333 /// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
334 #[cfg(all(feature = "dbus", feature = "zbus"))]
get_capabilities() -> Result<Vec<String>>335 pub fn get_capabilities() -> Result<Vec<String>> {
336     if std::env::var(DBUS_SWITCH_VAR).is_ok() {
337         dbus_rs::get_capabilities()
338     } else {
339         zbus_rs::get_capabilities()
340     }
341 }
342 
343 /// Returns a struct containing `ServerInformation`.
344 ///
345 /// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
346 /// running.
347 ///
348 /// (zbus only)
349 #[cfg(all(feature = "zbus", not(feature = "dbus")))]
get_server_information() -> Result<ServerInformation>350 pub fn get_server_information() -> Result<ServerInformation> {
351     zbus_rs::get_server_information()
352 }
353 
354 /// Returns a struct containing `ServerInformation`.
355 ///
356 /// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
357 /// running.
358 ///
359 /// (dbus-rs only)
360 #[cfg(all(feature = "dbus", not(feature = "zbus")))]
get_server_information() -> Result<ServerInformation>361 pub fn get_server_information() -> Result<ServerInformation> {
362     dbus_rs::get_server_information()
363 }
364 
365 /// Returns a struct containing `ServerInformation`.
366 ///
367 /// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
368 /// running.
369 ///
370 /// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
371 #[cfg(all(feature = "dbus", feature = "zbus"))]
get_server_information() -> Result<ServerInformation>372 pub fn get_server_information() -> Result<ServerInformation> {
373     if std::env::var(DBUS_SWITCH_VAR).is_ok() {
374         dbus_rs::get_server_information()
375     } else {
376         zbus_rs::get_server_information()
377     }
378 }
379 
380 /// Return value of `get_server_information()`.
381 #[derive(Debug)]
382 #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
383 #[cfg_attr(feature = "zbus", derive(zvariant_derive::Type))]
384 pub struct ServerInformation {
385     /// The product name of the server.
386     pub name: String,
387     /// The vendor name.
388     pub vendor: String,
389     /// The server's version string.
390     pub version: String,
391     /// The specification version the server is compliant with.
392     pub spec_version: String,
393 }
394 
395 /// Strictly internal.
396 /// The NotificationServer implemented here exposes a "Stop" function.
397 /// stops the notification server
398 #[cfg(all(feature = "server", unix, not(target_os = "macos")))]
399 #[doc(hidden)]
stop_server()400 pub fn stop_server() {
401     #[cfg(feature = "dbus")]
402     dbus_rs::stop_server()
403 }
404 
405 /// Listens for the `ActionInvoked(UInt32, String)` Signal.
406 ///
407 /// No need to use this, check out [`NotificationHandle::wait_for_action`]
408 /// (xdg only)
409 #[cfg(all(feature = "zbus", not(feature = "dbus")))]
410 // #[deprecated(note="please use [`NotificationHandle::wait_for_action`]")]
handle_action<F>(id: u32, func: F) where F: FnOnce(&ActionResponse),411 pub fn handle_action<F>(id: u32, func: F)
412 where
413     F: FnOnce(&ActionResponse),
414 {
415     zbus_rs::handle_action(id, func)
416 }
417 
418 /// Listens for the `ActionInvoked(UInt32, String)` Signal.
419 ///
420 /// No need to use this, check out [`NotificationHandle::wait_for_action`]
421 /// (xdg only)
422 #[cfg(all(feature = "dbus", not(feature = "zbus")))]
423 // #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
handle_action<F>(id: u32, func: F) where F: FnOnce(&ActionResponse),424 pub fn handle_action<F>(id: u32, func: F)
425 where
426     F: FnOnce(&ActionResponse),
427 {
428     dbus_rs::handle_action(id, func)
429 }
430 
431 /// Listens for the `ActionInvoked(UInt32, String)` Signal.
432 ///
433 /// No need to use this, check out [`NotificationHandle::wait_for_action`]
434 /// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
435 #[cfg(all(feature = "dbus", feature = "zbus"))]
436 // #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
handle_action<F>(id: u32, func: F) where F: FnOnce(&ActionResponse),437 pub fn handle_action<F>(id: u32, func: F)
438 where
439     F: FnOnce(&ActionResponse),
440 {
441     if std::env::var(DBUS_SWITCH_VAR).is_ok() {
442         dbus_rs::handle_action(id, func)
443     } else {
444         zbus_rs::handle_action(id, func)
445     }
446 }
447 
448 /// Reased passed to `NotificationClosed` Signal
449 ///
450 /// ## Specification
451 /// As listed under [Table 8. `NotificationClosed` Parameters](https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html#idm46350804042704)
452 #[derive(Copy, Clone, Debug)]
453 pub enum CloseReason {
454     /// The notification expired
455     Expired,
456     /// The notification was dismissed by the user
457     Dismissed,
458     /// The notification was closed by a call to `CloseNotification`
459     CloseAction,
460     /// Undefined/Reserved reason
461     Other(u32),
462 }
463 
464 impl From<u32> for CloseReason {
from(raw_reason: u32) -> Self465     fn from(raw_reason: u32) -> Self {
466         match raw_reason {
467             1 => CloseReason::Expired,
468             2 => CloseReason::Dismissed,
469             3 => CloseReason::CloseAction,
470             other => CloseReason::Other(other),
471         }
472     }
473 }
474 
475 /// Helper Trait implemented by `Fn()`
476 pub trait ActionResponseHandler {
call(self, response: &ActionResponse)477     fn call(self, response: &ActionResponse);
478 }
479 
480 // impl<F: Send + Sync + 'static> ActionResponseHandler for F
481 impl<F> ActionResponseHandler for F
482 where
483     F: FnOnce(&ActionResponse),
484 {
call(self, res: &ActionResponse)485     fn call(self, res: &ActionResponse) {
486         (self)(res)
487     }
488 }
489 
490 /// Response to an action
491 pub enum ActionResponse<'a> {
492     /// Custom Action configured by the Notification.
493     Custom(&'a str),
494 
495     /// The Notification was closed.
496     Closed(CloseReason),
497 }
498 
499 impl<'a> From<&'a str> for ActionResponse<'a> {
from(raw: &'a str) -> Self500     fn from(raw: &'a str) -> Self {
501         Self::Custom(raw)
502     }
503 }
504 
505 /// Your handy callback for the `Close` signal of your Notification.
506 ///
507 /// This is implemented by `Fn()` and `Fn(CloseReason)`, so there is probably no good reason for you to manually implement this trait.
508 /// Should you find one anyway, please notify me and I'll gladly remove this obviously redundant comment.
509 pub trait CloseHandler<T> {
510     /// This is called with the [`CloseReason`].
call(&self, reason: CloseReason)511     fn call(&self, reason: CloseReason);
512 }
513 
514 impl<F> CloseHandler<CloseReason> for F
515 where
516     F: Fn(CloseReason),
517 {
call(&self, reason: CloseReason)518     fn call(&self, reason: CloseReason) {
519         self(reason)
520     }
521 }
522 
523 impl<F> CloseHandler<()> for F
524 where
525     F: Fn(),
526 {
call(&self, _: CloseReason)527     fn call(&self, _: CloseReason) {
528         self()
529     }
530 }
531