Skip to main content

ravenrs_platform_native/
notice.rs

1//! The system's own notifications, for what must reach someone outside the window.
2
3/// Shows notifications in the system's own place: the Notification Center on macOS,
4/// a toast on Windows and the notification server on Linux, which plain GNOME has
5/// even without a tray. Needs the `notifications` feature.
6///
7/// ```no_run
8/// use ravenrs_platform_native::SystemNotifier;
9///
10/// let notifier = SystemNotifier::new("Notes").bundle_id("com.example.notes");
11/// std::thread::spawn(move || notifier.send("Saved", "Your notes are safe."));
12/// ```
13#[derive(Clone, Debug)]
14#[cfg_attr(not(feature = "notifications"), allow(dead_code, reason = "nothing to show them with"))]
15pub struct SystemNotifier {
16    name: String,
17    bundle_id: Option<String>,
18    window: Option<String>,
19}
20
21impl SystemNotifier {
22    /// Notifications from the app called `name`.
23    pub fn new(name: impl Into<String>) -> Self {
24        Self { name: name.into(), bundle_id: None, window: None }
25    }
26
27    /// The identifier in the app's `Info.plist`. On macOS it says whose the
28    /// notifications are; without one the system stops to ask for an app.
29    pub fn bundle_id(mut self, id: impl Into<String>) -> Self {
30        self.bundle_id = Some(id.into());
31        self
32    }
33
34    /// On Linux, a click on the notification brings back the window of the running
35    /// app with this [`AppIdentity`](crate::AppIdentity) id, if it closes to the tray.
36    /// The other systems do not tell of the click.
37    pub fn opens_window(mut self, id: impl Into<String>) -> Self {
38        self.window = Some(id.into());
39        self
40    }
41
42    /// Shows one. It waits for the system to answer, so call it away from the UI
43    /// thread. `Ok` means the system took it: the person may still have them turned off.
44    pub fn send(&self, title: &str, body: &str) -> Result<(), String> {
45        system::send(self, title, body)
46    }
47}
48
49#[cfg(feature = "notifications")]
50mod system {
51    use super::SystemNotifier;
52
53    pub(super) fn send(notifier: &SystemNotifier, title: &str, body: &str) -> Result<(), String> {
54        #[cfg(target_os = "macos")]
55        if let Some(id) = &notifier.bundle_id {
56            // Refused outside the installed app, and then it goes out in Finder's name.
57            let _ = notify_rust::set_application(id);
58        }
59        let mut notification = notify_rust::Notification::new();
60        notification.appname(&notifier.name).summary(title).body(body);
61        show(notifier, notification)
62    }
63
64    #[cfg(all(unix, not(target_os = "macos")))]
65    fn show(notifier: &SystemNotifier, mut notification: notify_rust::Notification) -> Result<(), String> {
66        notification.action("default", &notifier.name);
67        let handle = notification.show().map_err(|e| e.to_string())?;
68        if let Some(id) = notifier.window.clone() {
69            // Waiting for the click lasts as long as the notification does.
70            std::thread::spawn(move || {
71                handle.wait_for_action(|action| {
72                    if action == "default" {
73                        crate::tray::ask_running_copy(&id);
74                    }
75                });
76            });
77        }
78        Ok(())
79    }
80
81    #[cfg(not(all(unix, not(target_os = "macos"))))]
82    fn show(_: &SystemNotifier, notification: notify_rust::Notification) -> Result<(), String> {
83        notification.show().map(|_| ()).map_err(|e| e.to_string())
84    }
85}
86
87#[cfg(not(feature = "notifications"))]
88mod system {
89    use super::SystemNotifier;
90
91    pub(super) fn send(_: &SystemNotifier, _: &str, _: &str) -> Result<(), String> {
92        Err("this build has no system notifications".to_owned())
93    }
94}