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    /// Manually close the notification
122    ///
123    /// # Example
124    ///
125    /// ```no_run
126    /// # use notify_rust::*;
127    /// let handle: NotificationHandle = Notification::new()
128    ///     .summary("oh no")
129    ///     .hint(notify_rust::Hint::Transient(true))
130    ///     .body("I'll be here till you close me!")
131    ///     .hint(Hint::Resident(true)) // does not work on kde
132    ///     .timeout(Timeout::Never) // works on kde and gnome
133    ///     .show()
134    ///     .unwrap();
135    /// // ... and then later
136    /// handle.close();
137    /// ```
138    pub fn close(self) {
139        match self.inner {
140            #[cfg(feature = "dbus")]
141            NotificationHandleInner::Dbus(inner) => inner.close(),
142            #[cfg(feature = "zbus")]
143            NotificationHandleInner::Zbus(inner) => block_on(inner.close()),
144        }
145    }
146
147    /// Executes a closure after the notification has closed.
148    ///
149    /// ## Example 1: *I don't care about why it closed* (the good ole API)
150    ///
151    /// ```no_run
152    /// # use notify_rust::Notification;
153    /// Notification::new().summary("Time is running out")
154    ///                    .body("This will go away.")
155    ///                    .icon("clock")
156    ///                    .show()
157    ///                    .unwrap()
158    ///                    .on_close(|| println!("closed"));
159    /// ```
160    ///
161    /// ## Example 2: *I **do** care about why it closed* (added in v4.5.0)
162    ///
163    /// ```no_run
164    /// # use notify_rust::Notification;
165    /// Notification::new().summary("Time is running out")
166    ///                    .body("This will go away.")
167    ///                    .icon("clock")
168    ///                    .show()
169    ///                    .unwrap()
170    ///                    .on_close(|reason| println!("closed: {:?}", reason));
171    /// ```
172    pub fn on_close<A>(self, handler: impl CloseHandler<A>) {
173        match self.inner {
174            #[cfg(feature = "dbus")]
175            NotificationHandleInner::Dbus(inner) => {
176                inner.wait_for_action(|action: &ActionResponse| {
177                    if let ActionResponse::Closed(reason) = action {
178                        handler.call(*reason);
179                    }
180                });
181            }
182            #[cfg(feature = "zbus")]
183            NotificationHandleInner::Zbus(inner) => {
184                block_on(inner.wait_for_action(|action: &ActionResponse| {
185                    if let ActionResponse::Closed(reason) = action {
186                        handler.call(*reason);
187                    }
188                }));
189            }
190        };
191    }
192
193    /// Replace the original notification with an updated version
194    /// ## Example
195    /// ```no_run
196    /// # use notify_rust::Notification;
197    /// let mut notification = Notification::new().summary("Latest News")
198    ///                                           .body("Bayern Dortmund 3:2")
199    ///                                           .show()
200    ///                                           .unwrap();
201    ///
202    /// std::thread::sleep_ms(1_500);
203    ///
204    /// notification.summary("Latest News (Correction)")
205    ///             .body("Bayern Dortmund 3:3");
206    ///
207    /// notification.update();
208    /// ```
209    /// Watch out for different implementations of the
210    /// notification server! On plasma5 for instance, you should also change the appname, so the old
211    /// message is really replaced and not just amended. Xfce behaves well, all others have not
212    /// been tested by the developer.
213    pub fn update(&mut self) {
214        match self.inner {
215            #[cfg(feature = "dbus")]
216            NotificationHandleInner::Dbus(ref mut inner) => inner.update(),
217            #[cfg(feature = "zbus")]
218            NotificationHandleInner::Zbus(ref mut inner) => inner.update(),
219        }
220    }
221
222    /// Returns the Handle's id.
223    pub fn id(&self) -> u32 {
224        match self.inner {
225            #[cfg(feature = "dbus")]
226            NotificationHandleInner::Dbus(ref inner) => inner.id,
227            #[cfg(feature = "zbus")]
228            NotificationHandleInner::Zbus(ref inner) => inner.id,
229        }
230    }
231}
232
233/// Required for `DerefMut`
234impl Deref for NotificationHandle {
235    type Target = Notification;
236
237    fn deref(&self) -> &Notification {
238        match self.inner {
239            #[cfg(feature = "dbus")]
240            NotificationHandleInner::Dbus(ref inner) => &inner.notification,
241            #[cfg(feature = "zbus")]
242            NotificationHandleInner::Zbus(ref inner) => &inner.notification,
243        }
244    }
245}
246
247/// Allow you to easily modify notification properties
248impl DerefMut for NotificationHandle {
249    fn deref_mut(&mut self) -> &mut Notification {
250        match self.inner {
251            #[cfg(feature = "dbus")]
252            NotificationHandleInner::Dbus(ref mut inner) => &mut inner.notification,
253            #[cfg(feature = "zbus")]
254            NotificationHandleInner::Zbus(ref mut inner) => &mut inner.notification,
255        }
256    }
257}
258
259#[cfg(feature = "dbus")]
260impl From<dbus_rs::DbusNotificationHandle> for NotificationHandleInner {
261    fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandleInner {
262        NotificationHandleInner::Dbus(handle)
263    }
264}
265
266#[cfg(feature = "zbus")]
267impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandleInner {
268    fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandleInner {
269        NotificationHandleInner::Zbus(handle)
270    }
271}
272
273#[cfg(feature = "dbus")]
274impl From<dbus_rs::DbusNotificationHandle> for NotificationHandle {
275    fn from(handle: dbus_rs::DbusNotificationHandle) -> NotificationHandle {
276        NotificationHandle {
277            inner: handle.into(),
278        }
279    }
280}
281
282#[cfg(feature = "zbus")]
283impl From<zbus_rs::ZbusNotificationHandle> for NotificationHandle {
284    fn from(handle: zbus_rs::ZbusNotificationHandle) -> NotificationHandle {
285        NotificationHandle {
286            inner: handle.into(),
287        }
288    }
289}
290
291// here be public functions
292
293// TODO: breaking change, wait for 5.0
294// #[cfg(all(feature = "dbus", feature = "zbus"))]
295//compile_error!("the z and d features are mutually exclusive");
296
297#[cfg(all(
298    not(any(feature = "dbus", feature = "zbus")),
299    unix,
300    not(target_os = "macos")
301))]
302compile_error!("you have to build with either zbus or dbus turned on");
303
304/// Which Dbus implementation are we using?
305#[derive(Copy, Clone, Debug)]
306pub enum DbusStack {
307    /// using [dbus-rs](https://docs.rs/dbus-rs)
308    Dbus,
309    /// using [zbus](https://docs.rs/zbus)
310    Zbus,
311}
312
313#[cfg(all(feature = "dbus", feature = "zbus"))]
314const DBUS_SWITCH_VAR: &str = "DBUSRS";
315
316#[cfg(all(feature = "zbus", not(feature = "dbus")))]
317pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
318    block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
319}
320
321#[cfg(all(feature = "async", feature = "zbus"))]
322pub(crate) async fn show_notification_async(
323    notification: &Notification,
324) -> Result<NotificationHandle> {
325    zbus_rs::connect_and_send_notification(notification)
326        .await
327        .map(Into::into)
328}
329
330#[cfg(all(feature = "async", feature = "zbus"))]
331pub(crate) async fn show_notification_async_at_bus(
332    notification: &Notification,
333    bus: NotificationBus,
334) -> Result<NotificationHandle> {
335    zbus_rs::connect_and_send_notification_at_bus(notification, bus)
336        .await
337        .map(Into::into)
338}
339
340#[cfg(all(feature = "dbus", not(feature = "zbus")))]
341pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
342    dbus_rs::connect_and_send_notification(notification).map(Into::into)
343}
344
345#[cfg(all(feature = "dbus", feature = "zbus"))]
346pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
347    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
348        dbus_rs::connect_and_send_notification(notification).map(Into::into)
349    } else {
350        block_on(zbus_rs::connect_and_send_notification(notification)).map(Into::into)
351    }
352}
353
354/// Get the currently used [`DbusStack`]
355///
356/// (zbus only)
357#[cfg(all(feature = "zbus", not(feature = "dbus")))]
358pub fn dbus_stack() -> Option<DbusStack> {
359    Some(DbusStack::Zbus)
360}
361
362/// Get the currently used [`DbusStack`]
363///
364/// (dbus-rs only)
365#[cfg(all(feature = "dbus", not(feature = "zbus")))]
366pub fn dbus_stack() -> Option<DbusStack> {
367    Some(DbusStack::Dbus)
368}
369
370/// Get the currently used [`DbusStack`]
371///
372/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
373#[cfg(all(feature = "dbus", feature = "zbus"))]
374pub fn dbus_stack() -> Option<DbusStack> {
375    Some(if std::env::var(DBUS_SWITCH_VAR).is_ok() {
376        DbusStack::Dbus
377    } else {
378        DbusStack::Zbus
379    })
380}
381
382/// Get the currently used [`DbusStack`]
383///
384/// neither zbus nor dbus-rs are configured
385#[cfg(all(not(feature = "dbus"), not(feature = "zbus")))]
386pub fn dbus_stack() -> Option<DbusStack> {
387    None
388}
389
390/// Get list of all capabilities of the running notification server.
391///
392/// (zbus only)
393#[cfg(all(feature = "zbus", not(feature = "dbus")))]
394pub fn get_capabilities() -> Result<Vec<String>> {
395    block_on(zbus_rs::get_capabilities())
396}
397
398/// Get list of all capabilities of the running notification server.
399///
400/// (dbus-rs only)
401#[cfg(all(feature = "dbus", not(feature = "zbus")))]
402pub fn get_capabilities() -> Result<Vec<String>> {
403    dbus_rs::get_capabilities()
404}
405
406/// Get list of all capabilities of the running notification server.
407///
408/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
409#[cfg(all(feature = "dbus", feature = "zbus"))]
410pub fn get_capabilities() -> Result<Vec<String>> {
411    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
412        dbus_rs::get_capabilities()
413    } else {
414        block_on(zbus_rs::get_capabilities())
415    }
416}
417
418/// Returns a struct containing `ServerInformation`.
419///
420/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
421/// running.
422///
423/// (zbus only)
424#[cfg(all(feature = "zbus", not(feature = "dbus")))]
425pub fn get_server_information() -> Result<ServerInformation> {
426    block_on(zbus_rs::get_server_information())
427}
428
429/// Returns a struct containing `ServerInformation`.
430///
431/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
432/// running.
433///
434/// (dbus-rs only)
435#[cfg(all(feature = "dbus", not(feature = "zbus")))]
436pub fn get_server_information() -> Result<ServerInformation> {
437    dbus_rs::get_server_information()
438}
439
440/// Returns a struct containing `ServerInformation`.
441///
442/// This struct contains `name`, `vendor`, `version` and `spec_version` of the notification server
443/// running.
444///
445/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
446#[cfg(all(feature = "dbus", feature = "zbus"))]
447pub fn get_server_information() -> Result<ServerInformation> {
448    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
449        dbus_rs::get_server_information()
450    } else {
451        block_on(zbus_rs::get_server_information())
452    }
453}
454
455/// Return value of `get_server_information()`.
456#[derive(Debug)]
457#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
458#[cfg_attr(feature = "zbus", derive(zvariant::Type))]
459pub struct ServerInformation {
460    /// The product name of the server.
461    pub name: String,
462    /// The vendor name.
463    pub vendor: String,
464    /// The server's version string.
465    pub version: String,
466    /// The specification version the server is compliant with.
467    pub spec_version: String,
468}
469
470// /// Strictly internal.
471// /// The NotificationServer implemented here exposes a "Stop" function.
472// /// stops the notification server
473// #[cfg(all(feature = "server", unix, not(target_os = "macos")))]
474// #[doc(hidden)]
475// pub fn stop_server() {
476//     #[cfg(feature = "dbus")]
477//     dbus_rs::stop_server()
478// }
479
480/// Listens for the `ActionInvoked(UInt32, String)` Signal.
481///
482/// No need to use this, check out [`NotificationHandle::wait_for_action`]
483/// (xdg only)
484#[cfg(all(feature = "zbus", not(feature = "dbus")))]
485// #[deprecated(note="please use [`NotificationHandle::wait_for_action`]")]
486pub fn handle_action<F>(id: u32, func: F)
487where
488    F: FnOnce(&ActionResponse),
489{
490    block_on(zbus_rs::handle_action(id, func));
491}
492
493/// Listens for the `ActionInvoked(UInt32, String)` Signal.
494///
495/// No need to use this, check out [`NotificationHandle::wait_for_action`]
496/// (xdg only)
497#[cfg(all(feature = "dbus", not(feature = "zbus")))]
498// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
499pub fn handle_action<F>(id: u32, func: F)
500where
501    F: FnOnce(&ActionResponse),
502{
503    dbus_rs::handle_action(id, func);
504}
505
506/// Listens for the `ActionInvoked(UInt32, String)` Signal.
507///
508/// No need to use this, check out [`NotificationHandle::wait_for_action`]
509/// both dbus-rs and zbus, switch via `$ZBUS_NOTIFICATION`
510#[cfg(all(feature = "dbus", feature = "zbus"))]
511// #[deprecated(note="please use `NotificationHandle::wait_for_action`")]
512pub fn handle_action<F>(id: u32, func: F)
513where
514    F: FnOnce(&ActionResponse),
515{
516    if std::env::var(DBUS_SWITCH_VAR).is_ok() {
517        dbus_rs::handle_action(id, func);
518    } else {
519        block_on(zbus_rs::handle_action(id, func));
520    }
521}
522
523/// Reason passed to `NotificationClosed` Signal
524///
525/// ## Specification
526/// As listed under [Table 8. `NotificationClosed` Parameters](https://specifications.freedesktop.org/notification-spec/latest/ar01s09.html#idm46350804042704)
527#[derive(Copy, Clone, Debug)]
528pub enum CloseReason {
529    /// The notification expired
530    Expired,
531    /// The notification was dismissed by the user
532    Dismissed,
533    /// The notification was closed by a call to `CloseNotification`
534    CloseAction,
535    /// Undefined/Reserved reason
536    Other(u32),
537}
538
539impl From<u32> for CloseReason {
540    fn from(raw_reason: u32) -> Self {
541        match raw_reason {
542            1 => CloseReason::Expired,
543            2 => CloseReason::Dismissed,
544            3 => CloseReason::CloseAction,
545            other => CloseReason::Other(other),
546        }
547    }
548}
549
550/// Helper Trait implemented by `Fn()`
551pub trait ActionResponseHandler {
552    fn call(self, response: &ActionResponse);
553}
554
555// impl<F: Send + Sync + 'static> ActionResponseHandler for F
556impl<F> ActionResponseHandler for F
557where
558    F: FnOnce(&ActionResponse),
559{
560    fn call(self, res: &ActionResponse) {
561        (self)(res);
562    }
563}
564
565/// Response to an action
566pub enum ActionResponse<'a> {
567    /// Custom Action configured by the Notification.
568    Custom(&'a str),
569
570    /// The Notification was closed.
571    Closed(CloseReason),
572}
573
574impl<'a> From<&'a str> for ActionResponse<'a> {
575    fn from(raw: &'a str) -> Self {
576        Self::Custom(raw)
577    }
578}
579
580/// Your handy callback for the `Close` signal of your Notification.
581///
582/// This is implemented by `Fn()` and `Fn(CloseReason)`, so there is probably no good reason for you to manually implement this trait.
583/// Should you find one anyway, please notify me and I'll gladly remove this obviously redundant comment.
584pub trait CloseHandler<T> {
585    /// This is called with the [`CloseReason`].
586    fn call(&self, reason: CloseReason);
587}
588
589impl<F> CloseHandler<CloseReason> for F
590where
591    F: Fn(CloseReason),
592{
593    fn call(&self, reason: CloseReason) {
594        self(reason);
595    }
596}
597
598impl<F> CloseHandler<()> for F
599where
600    F: Fn(),
601{
602    fn call(&self, _: CloseReason) {
603        self();
604    }
605}