mac_notification_sys/
lib.rs

1//! A very thin wrapper around NSNotifications
2#![warn(
3    missing_docs,
4    trivial_casts,
5    trivial_numeric_casts,
6    unused_import_braces,
7    unused_qualifications
8)]
9#![cfg(target_os = "macos")]
10#![allow(improper_ctypes)]
11
12pub mod error;
13mod notification;
14
15use error::{ApplicationError, NotificationError, NotificationResult};
16pub use notification::{MainButton, Notification, NotificationResponse, Sound};
17use objc2_foundation::NSString;
18use std::ops::Deref;
19use std::sync::Once;
20
21static INIT_APPLICATION_SET: Once = Once::new();
22
23mod sys {
24    use objc2::rc::Retained;
25    use objc2_foundation::{NSDictionary, NSString};
26    #[link(name = "notify")]
27    extern "C" {
28        pub fn sendNotification(
29            title: *const NSString,
30            subtitle: *const NSString,
31            message: *const NSString,
32            options: *const NSDictionary<NSString, NSString>,
33        ) -> Retained<NSDictionary<NSString, NSString>>;
34        pub fn setApplication(newbundleIdentifier: *const NSString) -> bool;
35        pub fn getBundleIdentifier(appName: *const NSString) -> *const NSString;
36    }
37}
38
39/// Delivers a new notification
40///
41/// Returns a `NotificationError` if a notification could not be delivered
42///
43/// # Example:
44///
45/// ```no_run
46/// # use mac_notification_sys::*;
47/// // deliver a silent notification
48/// let _ = send_notification("Title", None, "This is the body", None).unwrap();
49/// ```
50// #[deprecated(note="use `Notification::send`")]
51pub fn send_notification(
52    title: &str,
53    subtitle: Option<&str>,
54    message: &str,
55    options: Option<&Notification>,
56) -> NotificationResult<NotificationResponse> {
57    if let Some(options) = &options {
58        if let Some(delivery_date) = options.delivery_date {
59            ensure!(
60                delivery_date >= time::OffsetDateTime::now_utc().unix_timestamp() as f64,
61                NotificationError::ScheduleInThePast
62            );
63        }
64    };
65
66    let options = options.unwrap_or(&Notification::new()).to_dictionary();
67
68    ensure_application_set()?;
69
70    let dictionary_response = unsafe {
71        sys::sendNotification(
72            NSString::from_str(title).deref(),
73            NSString::from_str(subtitle.unwrap_or("")).deref(),
74            NSString::from_str(message).deref(),
75            options.deref(),
76        )
77    };
78    ensure!(
79        dictionary_response
80            .objectForKey(NSString::from_str("error").deref())
81            .is_none(),
82        NotificationError::UnableToDeliver
83    );
84
85    let response = NotificationResponse::from_dictionary(dictionary_response);
86
87    Ok(response)
88}
89
90/// Search for a possible BundleIdentifier of a given appname.
91/// Defaults to "com.apple.Finder" if no BundleIdentifier is found.
92pub fn get_bundle_identifier_or_default(app_name: &str) -> String {
93    get_bundle_identifier(app_name).unwrap_or_else(|| "com.apple.Finder".to_string())
94}
95
96/// Search for a BundleIdentifier of an given appname.
97pub fn get_bundle_identifier(app_name: &str) -> Option<String> {
98    unsafe {
99        sys::getBundleIdentifier(NSString::from_str(app_name).deref()) // *const NSString
100            .as_ref()
101    }
102    .map(NSString::to_string)
103}
104
105/// Sets the application if not already set
106fn ensure_application_set() -> NotificationResult<()> {
107    if INIT_APPLICATION_SET.is_completed() {
108        return Ok(());
109    };
110    let bundle = get_bundle_identifier_or_default("use_default");
111    set_application(&bundle)
112}
113
114/// Set the application which delivers or schedules a notification
115pub fn set_application(bundle_ident: &str) -> NotificationResult<()> {
116    let mut result = Err(ApplicationError::AlreadySet(bundle_ident.into()).into());
117    INIT_APPLICATION_SET.call_once(|| {
118        let was_set = unsafe { sys::setApplication(NSString::from_str(bundle_ident).deref()) };
119        result = if was_set {
120            Ok(())
121        } else {
122            Err(ApplicationError::CouldNotSet(bundle_ident.into()).into())
123        };
124    });
125    result
126}