Skip to main content

notify_rust/xdg/
mod.rs

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")]
6use dbus::ffidisp::Connection as DbusConnection;
7#[cfg(feature = "zbus")]
8use zbus::{block_on, zvariant};
9
10use crate::{error::*, notification::Notification};
11
12pub use crate::response::{
13    ActionResponse, CloseHandler, CloseReason, NotificationResponse,
14    ResponseHandler as ActionResponseHandler,
15};
16
17use std::ops::{Deref, DerefMut};
18
19#[cfg(feature = "dbus")]
20mod dbus_rs;
21#[cfg(all(feature = "dbus", not(feature = "zbus")))]
22use dbus_rs::bus;
23
24#[cfg(feature = "zbus")]
25mod zbus_rs;
26#[cfg(all(feature = "zbus", not(feature = "dbus")))]
27use zbus_rs::bus;
28
29#[cfg(all(feature = "dbus", feature = "zbus"))]
30mod bus;
31
32// #[cfg(all(feature = "server", feature = "dbus", unix, not(target_os = "macos")))]
33// pub mod server_dbus;
34
35// #[cfg(all(feature = "server", feature = "zbus", unix, not(target_os = "macos")))]
36// pub mod server_zbus;
37
38// #[cfg(all(feature = "server", unix, not(target_os = "macos")))]
39// pub mod server;
40
41#[cfg(not(feature = "debug_namespace"))]
42#[doc(hidden)]
43pub static NOTIFICATION_DEFAULT_BUS: &str = "org.freedesktop.Notifications";
44
45#[cfg(feature = "debug_namespace")]
46#[doc(hidden)]
47// #[deprecated]
48pub static NOTIFICATION_DEFAULT_BUS: &str = "de.hoodie.Notifications";
49
50#[doc(hidden)]
51pub static NOTIFICATION_INTERFACE: &str = "org.freedesktop.Notifications";
52
53#[doc(hidden)]
54pub static NOTIFICATION_OBJECTPATH: &str = "/org/freedesktop/Notifications";
55
56pub(crate) use bus::NotificationBus;
57
58#[derive(Debug)]
59enum NotificationHandleInner {
60    #[cfg(feature = "dbus")]
61    Dbus(dbus_rs::DbusNotificationHandle),
62
63    #[cfg(feature = "zbus")]
64    Zbus(zbus_rs::ZbusNotificationHandle),
65}
66
67/// A handle to a shown notification.
68///
69/// Keeps a connection alive to ensure actions work on certain desktops.
70#[derive(Debug)]
71pub struct NotificationHandle {
72    inner: NotificationHandleInner,
73}
74
75#[allow(dead_code)]
76impl NotificationHandle {
77    #[cfg(feature = "dbus")]
78    pub(crate) fn for_dbus(
79        id: u32,
80        connection: DbusConnection,
81        notification: Notification,
82    ) -> NotificationHandle {
83        NotificationHandle {
84            inner: dbus_rs::DbusNotificationHandle::new(id, connection, notification).into(),
85        }
86    }
87
88    #[cfg(feature = "zbus")]
89    pub(crate) fn for_zbus(
90        id: u32,
91        connection: zbus::Connection,
92        notification: Notification,
93    ) -> NotificationHandle {
94        NotificationHandle {
95            inner: zbus_rs::ZbusNotificationHandle::new(id, connection, notification).into(),
96        }
97    }
98
99    /// Waits for the user to act on a notification and then calls
100    /// `invocation_closure` with the name of the corresponding action.
101    pub fn wait_for_action<F>(self, invocation_closure: F)
102    where
103        F: FnOnce(&str),
104    {
105        match self.inner {
106            #[cfg(feature = "dbus")]
107            NotificationHandleInner::Dbus(inner) => {
108                let _ = inner.wait_for_action(|response: &NotificationResponse| match response {
109                    NotificationResponse::Default => invocation_closure("default"),
110                    NotificationResponse::Action(ref action) => invocation_closure(action),
111                    NotificationResponse::Reply(_) => { /* XDG does not support inline replies */ }
112                    NotificationResponse::Closed(_) => invocation_closure("__closed"),
113                });
114            }
115
116            #[cfg(feature = "zbus")]
117            NotificationHandleInner::Zbus(inner) => {
118                block_on(
119                    inner.wait_for_action(|response: &NotificationResponse| match response {
120                        NotificationResponse::Default => invocation_closure("default"),
121                        NotificationResponse::Action(ref action) => invocation_closure(action),
122                        NotificationResponse::Reply(_) => { /* XDG does not support inline replies */ }
123                        NotificationResponse::Closed(_) => invocation_closure("__closed"), // FIXME: remove backward compatibility with 5.0
124                    }),
125                );
126            }
127        };
128    }
129
130    /// Waits for the user to act on a notification and then calls `handler`
131    /// with a typed [`NotificationResponse`].
132    ///
133    /// This is the typed, forward-compatible replacement for [`wait_for_action`](Self::wait_for_action).
134    pub fn wait_for_response(self, handler: impl ActionResponseHandler) -> Result<()> {
135        match self.inner {
136            #[cfg(feature = "dbus")]
137            NotificationHandleInner::Dbus(inner) => inner.wait_for_action(handler),
138            #[cfg(feature = "zbus")]
139            NotificationHandleInner::Zbus(inner) => {
140                block_on(inner.wait_for_action(handler));
141                Ok(())
142            }
143        }
144    }
145
146    /// Returns a future that waits for the user to act on a notification and then calls
147    /// `invocation_closure` with the name of the corresponding action.
148    ///
149    /// # Panics
150    ///
151    /// Panics if called with a [`Dbus`](DbusStack::Dbus) backend.
152    ///
153    /// # Example
154    ///
155    /// ```no_run
156    /// # use notify_rust::*;
157    /// # use async_std::task::sleep;
158    /// # use std::time::Duration;
159    /// # use futures_lite::future::zip;
160    /// # async fn wait_for_action_async_example() -> Result<(), Box<dyn std::error::Error>> {
161    /// let handle: NotificationHandle = Notification::new()
162    ///     .action("do-stuff", "my fancy button")
163    ///     .show_async()
164    ///     .await?;
165    ///
166    /// let wait_future = handle.wait_for_action_async(|action| {
167    ///     // handle action
168    /// #   let _ = action;
169    /// });
170    /// let close_future = async {
171    ///     sleep(Duration::from_secs(5)).await;
172    ///     handle.close_async();
173    /// };
174    ///
175    /// // run both futures concurrently
176    /// # let _ =
177    /// zip(wait_future, close_future).await;
178    /// # Ok(())
179    /// # }
180    /// ```
181    // TODO: make this consume `self` in 5.0
182    #[cfg(feature = "zbus")]
183    pub async fn wait_for_action_async<F>(&self, invocation_closure: F)
184    where
185        F: FnOnce(&NotificationResponse),
186    {
187        match &self.inner {
188            #[cfg(feature = "dbus")]
189            NotificationHandleInner::Dbus(_) => {
190                unimplemented!("async methods are not supported with the `dbus` backend");
191            }
192            #[cfg(feature = "zbus")]
193            NotificationHandleInner::Zbus(inner) => inner.wait_for_action(invocation_closure).await,
194        }
195    }
196
197    /// Manually close the notification
198    ///
199    /// # Example
200    ///
201    /// ```no_run
202    /// # use notify_rust::*;
203    /// let handle: NotificationHandle = Notification::new()
204    ///     .summary("oh no")
205    ///     .hint(notify_rust::Hint::Transient(true))
206    ///     .body("I'll be here till you close me!")
207    ///     .hint(Hint::Resident(true)) // does not work on kde
208    ///     .timeout(Timeout::Never) // works on kde and gnome
209    ///     .show()
210    ///     .unwrap();
211    /// // ... and then later
212    /// handle.close();
213    /// ```
214    pub fn close(self) {
215        match self.inner {
216            #[cfg(feature = "dbus")]
217            NotificationHandleInner::Dbus(inner) => inner.close(),
218            #[cfg(feature = "zbus")]
219            NotificationHandleInner::Zbus(inner) => block_on(inner.close()),
220        }
221    }
222
223    /// Async version of [`close`](Self::close).
224    ///
225    /// # Panics
226    ///
227    /// Panics if called with a [`Dbus`](DbusStack::Dbus) backend.
228    #[cfg(feature = "zbus")]
229    pub async fn close_async(&self) {
230        match &self.inner {
231            #[cfg(feature = "dbus")]
232            NotificationHandleInner::Dbus(_) => {
233                unimplemented!("async methods are not supported with the `dbus` backend");
234            }
235            #[cfg(feature = "zbus")]
236            NotificationHandleInner::Zbus(inner) => inner.close().await,
237        }
238    }
239
240    /// Executes a closure after the notification has closed.
241    ///
242    /// ## Example 1: *I don't care about why it closed* (the good ole API)
243    ///
244    /// ```no_run
245    /// # use notify_rust::Notification;
246    /// Notification::new().summary("Time is running out")
247    ///                    .body("This will go away.")
248    ///                    .icon("clock")
249    ///                    .show()
250    ///                    .unwrap()
251    ///                    .on_close(|| println!("closed"));
252    /// ```
253    ///
254    /// ## Example 2: *I **do** care about why it closed* (added in v4.5.0)
255    ///
256    /// ```no_run
257    /// # use notify_rust::Notification;
258    /// Notification::new().summary("Time is running out")
259    ///                    .body("This will go away.")
260    ///                    .icon("clock")
261    ///                    .show()
262    ///                    .unwrap()
263    ///                    .on_close(|reason| println!("closed: {:?}", reason));
264    /// ```
265    // #[deprecated(
266    //     since = "4.18.0",
267    //     note = "Use `wait_for_response()` and match on `ActionResponse::Closed` instead"
268    // )]
269    pub fn on_close<A>(self, handler: impl CloseHandler<A>) {
270        match self.inner {
271            #[cfg(feature = "dbus")]
272            NotificationHandleInner::Dbus(inner) => {
273                let _ = inner.wait_for_action(|action: &NotificationResponse| {
274                    if let NotificationResponse::Closed(reason) = action {
275                        handler.call(*reason);
276                    }
277                });
278            }
279            #[cfg(feature = "zbus")]
280            NotificationHandleInner::Zbus(inner) => {
281                block_on(inner.wait_for_action(|action: &NotificationResponse| {
282                    if let NotificationResponse::Closed(reason) = action {
283                        handler.call(*reason);
284                    }
285                }));
286            }
287        };
288    }
289
290    /// Replace the original notification with an updated version
291    /// ## Example
292    /// ```no_run
293    /// # use notify_rust::Notification;
294    /// let mut notification = Notification::new().summary("Latest News")
295    ///                                           .body("Bayern Dortmund 3:2")
296    ///                                           .show()
297    ///                                           .unwrap();
298    ///
299    /// std::thread::sleep_ms(1_500);
300    ///
301    /// notification.summary("Latest News (Correction)")
302    ///             .body("Bayern Dortmund 3:3");
303    ///
304    /// notification.update().unwrap();
305    /// ```
306    /// Watch out for different implementations of the
307    /// notification server! On plasma5 for instance, you should also change the appname, so the old
308    /// message is really replaced and not just amended. Xfce behaves well, all others have not
309    /// been tested by the developer.
310    pub fn update(&mut self) -> Result<()> {
311        match self.inner {
312            #[cfg(feature = "dbus")]
313            NotificationHandleInner::Dbus(ref mut inner) => inner.update(),
314            #[cfg(feature = "zbus")]
315            NotificationHandleInner::Zbus(ref mut inner) => inner.update(),
316        }
317    }
318
319    /// Returns the handle's id.
320    pub fn id(&self) -> u32 {
321        match self.inner {
322            #[cfg(feature = "dbus")]
323            NotificationHandleInner::Dbus(ref inner) => inner.id,
324            #[cfg(feature = "zbus")]
325            NotificationHandleInner::Zbus(ref inner) => inner.id,
326        }
327    }
328}
329
330/// Required for [`DerefMut`].
331impl Deref for NotificationHandle {
332    type Target = Notification;
333
334    fn deref(&self) -> &Notification {
335        match self.inner {
336            #[cfg(feature = "dbus")]
337            NotificationHandleInner::Dbus(ref inner) => &inner.notification,
338            #[cfg(feature = "zbus")]
339            NotificationHandleInner::Zbus(ref inner) => &inner.notification,
340        }
341    }
342}
343
344/// Allows easy modification of notification properties.
345impl DerefMut for NotificationHandle {
346    fn deref_mut(&mut self) -> &mut Notification {
347        match self.inner {
348            #[cfg(feature = "dbus")]
349            NotificationHandleInner::Dbus(ref mut inner) => &mut inner.notification,
350            #[cfg(feature = "zbus")]
351            NotificationHandleInner::Zbus(ref mut inner) => &mut inner.notification,
352        }
353    }
354}
355
356#[cfg(feature = "dbus")]
357impl From<dbus_rs::DbusNotificationHandle> for NotificationHandleInner {
358    fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner {
359        NotificationHandleInner::Dbus(handle)
360    }
361}
362
363#[cfg(feature = "zbus")]
364impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandleInner {
365    fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner {
366        NotificationHandleInner::Zbus(handle)
367    }
368}
369
370#[cfg(feature = "dbus")]
371impl From<dbus_rs::DbusNotificationHandle> for NotificationHandle {
372    fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle {
373        NotificationHandle {
374            inner: handle.into(),
375        }
376    }
377}
378
379#[cfg(feature = "zbus")]
380impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandle {
381    fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandle {
382        NotificationHandle {
383            inner: handle.into(),
384        }
385    }
386}
387
388// here be public functions
389
390// TODO: breaking change, wait for 5.0
391// #[cfg(all(feature = "dbus", feature = "zbus"))]
392//compile_error!("the z and d features are mutually exclusive");
393
394#[cfg(all(
395    not(any(feature = "dbus", feature = "zbus")),
396    unix,
397    not(target_os = "macos")
398))]
399compile_error!("you have to build with either zbus or dbus turned on");
400
401/// Which D-Bus implementation is in use.
402#[derive(Copy, Clone, Debug)]
403pub enum DbusStack {
404    /// Using [dbus-rs](https://docs.rs/dbus-rs).
405    Dbus,
406    /// Using [zbus](https://docs.rs/zbus).
407    Zbus,
408}
409
410#[cfg(all(feature = "dbus", feature = "zbus"))]
411const DBUS_SWITCH_VAR: &str = "DBUSRS";
412
413#[cfg(all(feature = "zbus", not(feature = "dbus")))]
414pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
415    block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
416}
417
418#[cfg(feature = "zbus")]
419pub(crate) async fn show_notification_async(
420    notification: &Notification,
421) -> Result<NotificationHandle> {
422    zbus_rs::connect_and_send_notification(notification)
423        .await
424        .map(Into::into)
425}
426
427#[cfg(feature = "zbus")]
428pub(crate) async fn show_notification_async_at_bus(
429    notification: &Notification,
430    bus: NotificationBus,
431) -> Result<NotificationHandle> {
432    zbus_rs::connect_and_send_notification_at_bus(notification, bus)
433        .await
434        .map(Into::into)
435}
436
437#[cfg(all(feature = "dbus", not(feature = "zbus")))]
438pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
439    dbus_rs::connect_and_send_notification(notification).map(Into::into)
440}
441
442#[cfg(all(feature = "dbus", feature = "zbus"))]
443pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
444    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
445        dbus_rs::connect_and_send_notification(notification).map(Into::into)
446    } else {
447        block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
448    }
449}
450
451/// Get the currently active [`DbusStack`].
452///
453/// (zbus only)
454#[cfg(all(feature = "zbus", not(feature = "dbus")))]
455pub fn dbus_stack() -> Option<DbusStack> {
456    Some(DbusStack::Zbus)
457}
458
459/// Get the currently active [`DbusStack`].
460///
461/// (dbus-rs only)
462#[cfg(all(feature = "dbus", not(feature = "zbus")))]
463pub fn dbus_stack() -> Option<DbusStack> {
464    Some(DbusStack::Dbus)
465}
466
467/// Get the currently active [`DbusStack`].
468///
469/// Both dbus-rs and zbus are compiled in; switch via the `$DBUSRS` environment variable.
470#[cfg(all(feature = "dbus", feature = "zbus"))]
471pub fn dbus_stack() -> Option<DbusStack> {
472    Some(if std::env::var(DBUS_SWITCH_VAR).is_ok() {
473        DbusStack::Dbus
474    } else {
475        DbusStack::Zbus
476    })
477}
478
479/// Get the currently active [`DbusStack`].
480///
481/// Neither `zbus` nor `dbus-rs` are configured; always returns `None`.
482#[cfg(all(not(feature = "dbus"), not(feature = "zbus")))]
483pub fn dbus_stack() -> Option<DbusStack> {
484    None
485}
486
487/// Returns a list of all capabilities of the running notification server.
488///
489/// (zbus only)
490#[cfg(all(feature = "zbus", not(feature = "dbus")))]
491pub fn get_capabilities() -> Result<Vec<String>> {
492    block_on(zbus_rs::get_capabilities())
493}
494
495/// Returns a list of all capabilities of the running notification server.
496///
497/// (dbus-rs only)
498#[cfg(all(feature = "dbus", not(feature = "zbus")))]
499pub fn get_capabilities() -> Result<Vec<String>> {
500    dbus_rs::get_capabilities()
501}
502
503/// Returns a list of all capabilities of the running notification server.
504///
505/// Both dbus-rs and zbus are compiled in; switch via the `$DBUSRS` environment variable.
506#[cfg(all(feature = "dbus", feature = "zbus"))]
507pub fn get_capabilities() -> Result<Vec<String>> {
508    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
509        dbus_rs::get_capabilities()
510    } else {
511        block_on(zbus_rs::get_capabilities())
512    }
513}
514
515/// Returns a [`ServerInformation`] struct describing the running notification server.
516///
517/// The struct contains `name`, `vendor`, `version`, and `spec_version`.
518///
519/// (zbus only)
520#[cfg(all(feature = "zbus", not(feature = "dbus")))]
521pub fn get_server_information() -> Result<ServerInformation> {
522    block_on(zbus_rs::get_server_information())
523}
524
525/// Returns a [`ServerInformation`] struct describing the running notification server.
526///
527/// The struct contains `name`, `vendor`, `version`, and `spec_version`.
528///
529/// (dbus-rs only)
530#[cfg(all(feature = "dbus", not(feature = "zbus")))]
531pub fn get_server_information() -> Result<ServerInformation> {
532    dbus_rs::get_server_information()
533}
534
535/// Returns a [`ServerInformation`] struct describing the running notification server.
536///
537/// The struct contains `name`, `vendor`, `version`, and `spec_version`.
538///
539/// Both dbus-rs and zbus are compiled in; switch via the `$DBUSRS` environment variable.
540#[cfg(all(feature = "dbus", feature = "zbus"))]
541pub fn get_server_information() -> Result<ServerInformation> {
542    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
543        dbus_rs::get_server_information()
544    } else {
545        block_on(zbus_rs::get_server_information())
546    }
547}
548
549/// Return value of [`get_server_information()`].
550#[derive(Debug)]
551#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
552#[cfg_attr(feature = "zbus", derive(zvariant::Type))]
553pub struct ServerInformation {
554    /// The product name of the server.
555    pub name: String,
556    /// The vendor name.
557    pub vendor: String,
558    /// The server's version string.
559    pub version: String,
560    /// The specification version the server is compliant with.
561    pub spec_version: String,
562}
563
564// /// Strictly internal.
565// /// The NotificationServer implemented here exposes a "Stop" function.
566// /// stops the notification server
567// #[cfg(all(feature = "server", unix, not(target_os = "macos")))]
568// #[doc(hidden)]
569// pub fn stop_server() {
570//     #[cfg(feature = "dbus")]
571//     dbus_rs::stop_server()
572// }
573
574/// Listens for the `ActionInvoked(UInt32, String)` signal.
575///
576/// Prefer [`NotificationHandle::wait_for_action`] instead.
577/// (xdg only)
578#[cfg(all(feature = "zbus", not(feature = "dbus")))]
579// #[deprecated(note="please use [`NotificationHandle::wait_for_action`]")]
580pub fn handle_action<F>(id: u32, func: F) -> Result<()>
581where
582    F: FnOnce(&ActionResponse<'_>),
583{
584    block_on(zbus_rs::handle_action(id, action_response_adapter(func)));
585    Ok(())
586}
587
588/// Listens for the `ActionInvoked(UInt32, String)` signal.
589///
590/// Prefer [`NotificationHandle::wait_for_action`] instead.
591/// (xdg only)
592#[cfg(all(feature = "dbus", not(feature = "zbus")))]
593// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
594pub fn handle_action<F>(id: u32, func: F) -> Result<()>
595where
596    F: FnOnce(&ActionResponse<'_>),
597{
598    dbus_rs::handle_action(id, action_response_adapter(func))
599}
600
601/// Listens for the `ActionInvoked(UInt32, String)` signal.
602///
603/// Prefer [`NotificationHandle::wait_for_action`] instead.
604/// Both dbus-rs and zbus are compiled in; switch via the `$DBUSRS` environment variable.
605#[cfg(all(feature = "dbus", feature = "zbus"))]
606// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
607pub fn handle_action<F>(id: u32, func: F) -> Result<()>
608where
609    F: FnOnce(&ActionResponse<'_>),
610{
611    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
612        dbus_rs::handle_action(id, action_response_adapter(func))
613    } else {
614        block_on(zbus_rs::handle_action(id, action_response_adapter(func)));
615        Ok(())
616    }
617}
618
619/// Wraps an old-style `FnOnce(&ActionResponse)` into a new-style `FnOnce(&NotificationResponse)`
620/// so legacy callers of [`handle_action`] keep working.
621fn action_response_adapter<F>(func: F) -> impl FnOnce(&NotificationResponse)
622where
623    F: FnOnce(&ActionResponse<'_>),
624{
625    move |response: &NotificationResponse| match response {
626        NotificationResponse::Default => func(&ActionResponse::Custom("default")),
627        NotificationResponse::Action(ref s) => func(&ActionResponse::Custom(s.as_str())),
628        NotificationResponse::Reply(_) => { /* XDG does not support inline replies */ }
629        NotificationResponse::Closed(r) => func(&ActionResponse::Closed(*r)),
630    }
631}