Skip to main content

mac_notification_sys/
lib.rs

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