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