mac_notification_sys/
lib.rs1#![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
39pub 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
90pub 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
96pub fn get_bundle_identifier(app_name: &str) -> Option<String> {
98 unsafe {
99 sys::getBundleIdentifier(NSString::from_str(app_name).deref()) .as_ref()
101 }
102 .map(NSString::to_string)
103}
104
105fn 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
114pub 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}