mac_usernotifications/
auth.rs1use std::{cell::Cell, future::Future, ptr::NonNull};
7
8use block2::RcBlock;
9use futures_channel::oneshot;
10#[cfg(feature = "blocking-wrappers")]
11use futures_lite::future;
12use objc2::runtime::Bool;
13use objc2_user_notifications::{UNAuthorizationOptions, UNUserNotificationCenter};
14
15use crate::{Error, check_bundle, settings::NotificationSettings, worker};
16
17fn request_auth_inner() -> impl Future<Output = Result<bool, Error>> + Send + 'static {
18 log::trace!("dispatching to worker");
19 let (tx, rx) = oneshot::channel::<Result<bool, Error>>();
20 worker::dispatch(move || {
21 log::debug!("closure entered on worker");
22 let center = UNUserNotificationCenter::currentNotificationCenter();
23
24 let tx = Cell::new(Some(tx));
25 log::debug!("calling requestAuthorizationWithOptions");
26 center.requestAuthorizationWithOptions_completionHandler(
27 UNAuthorizationOptions::Alert | UNAuthorizationOptions::Sound,
28 &RcBlock::new(move |granted: Bool, err: *mut objc2_foundation::NSError| {
29 log::trace!(
30 "authorization completed (granted={}, err.is_null={})",
31 granted.as_bool(),
32 err.is_null()
33 );
34 let Some(tx) = tx.take() else {
35 log::warn!("completion fired twice");
36 return;
37 };
38
39 if tx.send(Ok(granted.as_bool())).is_err() {
40 log::warn!("receiver dropped before completion");
41 }
42 }),
43 );
44 log::debug!("requestAuthorizationWithOptions returned");
45 });
46 async move { rx.await.unwrap_or(Err(Error::NotificationRejected)) }
47}
48
49pub async fn request_auth() -> Result<bool, Error> {
54 check_bundle()?;
55 request_auth_inner().await
56}
57
58#[cfg(feature = "blocking-wrappers")]
66pub fn request_auth_blocking() -> Result<bool, Error> {
67 check_bundle()?;
68 future::block_on(request_auth_inner())
69}
70
71fn get_settings_inner() -> impl Future<Output = Result<NotificationSettings, Error>> + Send + 'static
72{
73 let (tx, rx) = oneshot::channel::<NotificationSettings>();
74 let tx = Cell::new(Some(tx));
75 worker::dispatch(move || {
76 UNUserNotificationCenter::currentNotificationCenter()
77 .getNotificationSettingsWithCompletionHandler(&RcBlock::new(
78 move |settings: NonNull<objc2_user_notifications::UNNotificationSettings>| {
79 let settings_ref = unsafe { settings.as_ref() };
80 let result = NotificationSettings {
81 authorization_status: settings_ref.authorizationStatus().into(),
82 alert_enabled: settings_ref.alertSetting().into(),
83 badge_enabled: settings_ref.badgeSetting().into(),
84 sound_enabled: settings_ref.soundSetting().into(),
85 lock_screen_enabled: settings_ref.lockScreenSetting().into(),
86 notification_center_enabled: settings_ref
87 .notificationCenterSetting()
88 .into(),
89 };
90 if let Some(sender) = tx.take() {
91 let _ = sender.send(result);
92 }
93 },
94 ));
95 });
96 async move { rx.await.map_err(|_| Error::NotificationRejected) }
97}
98
99pub async fn get_notification_settings() -> Result<NotificationSettings, Error> {
104 check_bundle()?;
105 get_settings_inner().await
106}
107
108#[cfg(feature = "blocking-wrappers")]
112pub fn get_notification_settings_blocking() -> Result<NotificationSettings, Error> {
113 check_bundle()?;
114 future::block_on(get_settings_inner())
115}