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