use crate::{
error::*,
notification::Notification,
notification_id::NotificationId,
response::{CloseHandler, NotificationResponse, ResponseHandler},
CloseReason, Timeout, Urgency,
};
use std::{ops::Deref, time::Duration};
pub use mac_usernotifications::Error as MacOsError;
#[derive(Debug)]
pub struct NotificationHandle {
notification: Notification,
inner: mac_usernotifications::NotificationHandle,
}
impl NotificationHandle {
pub fn new(notification: Notification) -> Self {
show_notification_blocking(¬ification)
.expect("UNUserNotificationCenter: failed to deliver notification")
}
pub(crate) fn from_parts(
notification: Notification,
inner: mac_usernotifications::NotificationHandle,
) -> Self {
Self {
notification,
inner,
}
}
pub fn id(&self) -> NotificationId {
NotificationId::Mac(self.inner.notification_id().to_owned())
}
pub fn update(&mut self) -> Result<()> {
let nid = self.inner.notification_id().to_owned();
self.notification.id = Some(NotificationId::Mac(nid));
show_notification_blocking(&self.notification)?;
Ok(())
}
pub async fn update_async(&mut self) -> Result<()> {
let nid = self.inner.notification_id().to_owned();
self.notification.id = Some(NotificationId::Mac(nid));
show_notification_async(&self.notification).await?;
Ok(())
}
pub fn close(&self) {
mac_usernotifications::blocking::close_delivered(self.inner.notification_id());
}
pub async fn close_async(&self) {
mac_usernotifications::close_delivered(self.inner.notification_id()).await;
}
pub fn wait_for_action<F>(self, invocation_closure: F)
where
F: FnOnce(&str),
{
match mac_usernotifications::block_on_current(self.inner.response()).flatten() {
Ok(response) => {
let action = if response.is_dismiss_action() {
"__closed"
} else if response.is_default_action() {
"default"
} else {
&response.action_identifier
};
invocation_closure(action);
}
Err(error) => {
log::error!("failed to get response: {error}");
invocation_closure("__closed");
}
}
}
pub fn wait_for_response(self, handler: impl ResponseHandler) -> Result<()> {
match mac_usernotifications::block_on_current(self.inner.response()).flatten() {
Ok(response) => {
handler.call(&response.into());
Ok(())
}
Err(error) => {
log::error!("failed to get response: {error}");
Err(Error::from(ErrorKind::Msg(format!(
"failed to get notification response: {error}"
))))
}
}
}
pub fn on_close<A>(self, handler: impl CloseHandler<A>) {
match mac_usernotifications::block_on_current(self.inner.response()).flatten() {
Ok(response) => {
if let Some(close_reason) = response.close_reason {
handler.call(close_reason.into());
} else {
handler.call(CloseReason::Dismissed);
}
}
Err(error) => {
log::error!("failed to get response: {error}");
handler.call(CloseReason::Dismissed);
}
}
}
}
impl From<mac_usernotifications::CloseReason> for CloseReason {
fn from(close_reason: mac_usernotifications::CloseReason) -> Self {
match close_reason {
mac_usernotifications::CloseReason::Dismissed => CloseReason::Dismissed,
mac_usernotifications::CloseReason::Expired => CloseReason::Expired,
}
}
}
impl From<mac_usernotifications::NotificationResponse> for NotificationResponse {
fn from(resp: mac_usernotifications::NotificationResponse) -> Self {
if resp.is_dismiss_action() {
NotificationResponse::Closed(CloseReason::Dismissed)
} else if resp.is_default_action() {
NotificationResponse::Default
} else if let Some(ref text) = resp.reply_text {
NotificationResponse::Reply(text.clone())
} else {
NotificationResponse::Action(resp.action_identifier.clone())
}
}
}
impl From<Urgency> for mac_usernotifications::InterruptionLevel {
fn from(val: Urgency) -> Self {
match val {
Urgency::Low => mac_usernotifications::InterruptionLevel::Passive,
Urgency::Normal => mac_usernotifications::InterruptionLevel::Active,
Urgency::Critical => mac_usernotifications::InterruptionLevel::TimeSensitive,
}
}
}
impl std::panic::UnwindSafe for NotificationHandle {}
impl std::panic::RefUnwindSafe for NotificationHandle {}
impl Deref for NotificationHandle {
type Target = Notification;
fn deref(&self) -> &Notification {
&self.notification
}
}
impl std::ops::DerefMut for NotificationHandle {
fn deref_mut(&mut self) -> &mut Notification {
&mut self.notification
}
}
impl From<&Notification> for mac_usernotifications::Notification {
fn from(n: &Notification) -> Self {
let mut un = mac_usernotifications::Notification::new()
.title(&n.summary)
.message(&n.body)
.maybe_subtitle(n.subtitle.as_deref())
.maybe_sound(n.sound_name.as_deref());
for chunk in n.actions.chunks(2) {
if let (Some(id), Some(label)) = (chunk.first(), chunk.get(1)) {
un = un.action(mac_usernotifications::Action::button(id, label));
}
}
if let Timeout::Milliseconds(ms) = n.timeout {
un = un.timeout(Duration::from_millis(ms as u64));
}
if let Some(ref path) = n.path_to_image {
un = un.image_path(path);
}
if let Some(ref nid) = n.id {
let id_str = match nid {
NotificationId::Mac(ref string_id) => string_id.clone(),
NotificationId::Xdg(num) => num.to_string(),
};
un = un.id(&id_str);
}
if let Some(level) = n.interruption_level {
un = un.interruption_level(level);
}
un
}
}
pub(crate) fn show_notification(notification: &Notification) -> Result<NotificationHandle> {
show_notification_blocking(notification)
}
pub(crate) fn show_notification_blocking(
notification: &Notification,
) -> Result<NotificationHandle> {
let un = mac_usernotifications::Notification::from(notification);
let inner = un.send_blocking()?;
Ok(NotificationHandle::from_parts(notification.clone(), inner))
}
pub(crate) async fn show_notification_async(
notification: &Notification,
) -> Result<NotificationHandle> {
let un = mac_usernotifications::Notification::from(notification);
let inner = un.send().await?;
Ok(NotificationHandle::from_parts(notification.clone(), inner))
}
pub(crate) fn schedule_notification(
notification: &Notification,
delivery_date: f64,
) -> Result<NotificationHandle> {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs_f64();
let delay = (delivery_date - now).max(0.1);
let un = mac_usernotifications::Notification::from(notification)
.schedule_in(Duration::from_secs_f64(delay));
let inner = un.send_blocking()?;
Ok(NotificationHandle::from_parts(notification.clone(), inner))
}