use crate::{error::*, notification::Notification};
pub use mac_notification_sys::error::{ApplicationError, Error as MacOsError, NotificationError};
#[derive(Debug)]
pub struct NotificationHandle {
notification: Option<Notification>,
}
impl NotificationHandle {
pub fn new(notification: Notification) -> Self {
Self {
notification: Some(notification),
}
}
pub fn wait_for_action<F>(mut self, invocation_closure: F)
where
F: FnOnce(&str),
{
log::trace!("wait_for_action");
let Some(notification) = self.notification.take() else {
return;
};
match send_mac_notification(
¬ification,
Options {
asynchronous: false,
force_close_button: false,
delivery_date: None,
},
)
.unwrap_or(mac_notification_sys::NotificationResponse::None)
{
mac_notification_sys::NotificationResponse::ActionButton(ref label) => {
invocation_closure(label);
}
mac_notification_sys::NotificationResponse::Click => invocation_closure("default"),
mac_notification_sys::NotificationResponse::Reply(ref text) => {
invocation_closure(text);
}
mac_notification_sys::NotificationResponse::CloseButton(_)
| mac_notification_sys::NotificationResponse::None => invocation_closure("__closed"),
}
}
pub fn wait_for_response(
mut self,
handler: impl crate::response::ResponseHandler,
) -> Result<()> {
use crate::response::{CloseReason, NotificationResponse};
log::trace!("wait_for_response");
let Some(notification) = self.notification.take() else {
return Ok(());
};
let response = send_mac_notification(
¬ification,
Options {
asynchronous: false,
force_close_button: false,
delivery_date: None,
},
)?;
use mac_notification_sys::NotificationResponse as MacResponse;
use NotificationResponse::*;
let response = match response {
MacResponse::ActionButton(label) => Action(label),
MacResponse::Click => Default,
MacResponse::Reply(text) => Reply(text),
MacResponse::CloseButton(_) => Closed(CloseReason::Dismissed),
MacResponse::None => Closed(CloseReason::Expired),
};
handler.call(&response);
Ok(())
}
pub fn on_close<A>(mut self, handler: impl crate::response::CloseHandler<A>) {
let Some(notification) = self.notification.take() else {
return;
};
let response = send_mac_notification(
¬ification,
Options {
asynchronous: false,
force_close_button: true,
delivery_date: None,
},
)
.unwrap_or(mac_notification_sys::NotificationResponse::None);
log::trace!("response: {response:?}");
match response {
mac_notification_sys::NotificationResponse::CloseButton(_) => {
handler.call(crate::CloseReason::Dismissed);
}
mac_notification_sys::NotificationResponse::None => {
handler.call(crate::CloseReason::Expired);
}
_ => {}
}
}
}
impl Drop for NotificationHandle {
fn drop(&mut self) {
log::trace!(
"not using handle, sending immediately: {:#?}",
self.notification
);
let Some(notification) = self.notification.take() else {
return;
};
send_mac_notification(
¬ification,
Options {
asynchronous: true,
force_close_button: false,
delivery_date: None,
},
)
.ok();
}
}
struct Options {
asynchronous: bool,
force_close_button: bool,
delivery_date: Option<f64>,
}
fn send_mac_notification(
notification: &Notification,
Options {
asynchronous,
force_close_button,
delivery_date,
}: Options,
) -> Result<mac_notification_sys::NotificationResponse> {
let labels: Vec<&str> = notification
.actions
.chunks(2)
.filter_map(|pair| pair.get(1).map(|s| s.as_str()))
.collect();
let label_to_id: std::collections::HashMap<&str, &str> = notification
.actions
.chunks(2)
.filter_map(|pair| match pair {
[id, label] => Some((label.as_str(), id.as_str())),
_ => None,
})
.collect();
let main_button = match labels.as_slice() {
[] => None,
[single] => Some(mac_notification_sys::MainButton::SingleAction(single)),
_ => Some(mac_notification_sys::MainButton::DropdownActions(
"Options", &labels,
)),
};
let mut n = mac_notification_sys::Notification::default();
n.title(notification.summary.as_str())
.message(¬ification.body)
.maybe_subtitle(notification.subtitle.as_deref())
.maybe_sound(notification.sound_name.as_deref());
if let Some(ref btn) = main_button {
n.main_button(btn.clone());
}
if asynchronous {
log::trace!("Notification will be sent asynchronously");
} else {
log::trace!("Notification will be sent synchronously");
if force_close_button {
n.close_button("Close");
}
}
n.asynchronous(asynchronous);
if let Some(ref image_path) = notification.path_to_image {
n.content_image(image_path);
}
if let Some(delivery_date) = delivery_date {
n.delivery_date(delivery_date);
}
let response = n.send().map_err(Into::<Error>::into)?;
let response = match response {
mac_notification_sys::NotificationResponse::ActionButton(ref label) => {
if let Some(&id) = label_to_id.get(label.as_str()) {
mac_notification_sys::NotificationResponse::ActionButton(id.to_owned())
} else if let Some(first_id) = notification.actions.first().map(|s| s.as_str()) {
mac_notification_sys::NotificationResponse::ActionButton(first_id.to_owned())
} else {
response
}
}
other => other,
};
Ok(response)
}
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
Ok(
#[allow(deprecated)]
NotificationHandle::new(notification.clone()),
)
}
pub(crate) fn schedule_notification(
notification: &Notification,
delivery_date: f64,
) -> Result<NotificationHandle> {
send_mac_notification(
notification,
Options {
asynchronous: true,
force_close_button: false,
delivery_date: Some(delivery_date),
},
)?;
Ok(
#[allow(deprecated)]
NotificationHandle { notification: None },
)
}