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