mac_notification_sys/
lib.rs1#![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
45pub 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
96pub 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
102pub fn get_bundle_identifier(app_name: &str) -> Option<String> {
104 unsafe {
105 sys::getBundleIdentifier(NSString::from_str(app_name).deref()) .as_ref()
107 }
108 .map(NSString::to_string)
109}
110
111fn 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
120pub 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}