Skip to content

Commit

Permalink
feat: allow playing default notification sound
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir authored and hoodie committed Jul 23, 2023
1 parent 577fbdd commit c0ffee2
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 39 deletions.
8 changes: 8 additions & 0 deletions examples/sound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ fn main() {
.sound("Ping")
.send()
.unwrap();

Notification::default()
.title("🐟")
.message("Submarine")
.sound("Submarine")
.send()
.unwrap();

Notification::default()
.title("🥱")
.message("Default")
.sound(Sound::Default)
.send()
.unwrap();
}
11 changes: 9 additions & 2 deletions objc/notify.m
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,16 @@ BOOL setApplication(NSString* newbundleIdentifier)
userNotification.informativeText = message;

// Notification sound
if (options[@"sound"] && ![options[@"sound"] isEqualToString:@""] && ![options[@"sound"] isEqualToString:@"_mute"])
if (options[@"sound"] && ![options[@"sound"] isEqualToString:@""])
{
userNotification.soundName = options[@"sound"];
if ([options[@"sound"] isEqualToString:@"NSUserNotificationDefaultSoundName"])
{
userNotification.soundName = NSUserNotificationDefaultSoundName;
}
else
{
userNotification.soundName = options[@"sound"];
}
}

// Delivery Date/Schedule
Expand Down
22 changes: 15 additions & 7 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Custom errors for mac-notification-sys.

use std::fmt;
use std::error;
use std::fmt;

/// Custom Result type for mac-notification-sys.
pub type NotificationResult<T> = Result<T, Error>;
Expand All @@ -21,13 +21,19 @@ mod application {
impl fmt::Display for ApplicationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ApplicationError::AlreadySet(e) => write!(f, "Application '{}' can only be set once.", e),
ApplicationError::CouldNotSet(e) => write!(f, "Could not set application '{}', using default \"com.apple.Termial\"", e),
ApplicationError::AlreadySet(e) => {
write!(f, "Application '{}' can only be set once.", e)
}
ApplicationError::CouldNotSet(e) => write!(
f,
"Could not set application '{}', using default \"com.apple.Termial\"",
e
),
}
}
}

impl error::Error for ApplicationError { }
impl error::Error for ApplicationError {}
}

mod notification {
Expand All @@ -48,14 +54,16 @@ mod notification {
impl fmt::Display for NotificationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
NotificationError::ScheduleInThePast => write!(f, "Can not schedule notification in the past"),
NotificationError::ScheduleInThePast => {
write!(f, "Can not schedule notification in the past")
}
NotificationError::UnableToSchedule => write!(f, "Could not schedule notification"),
NotificationError::UnableToDeliver => write!(f, "Could not deliver notification"),
}
}
}

impl error::Error for NotificationError { }
impl error::Error for NotificationError {}
}

pub use self::application::ApplicationError;
Expand All @@ -67,7 +75,7 @@ pub enum Error {
/// Application related Error
Application(ApplicationError),
/// Notification related Error
Notification(NotificationError)
Notification(NotificationError),
}

impl fmt::Display for Error {
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub mod error;
mod notification;

use error::{ApplicationError, NotificationError, NotificationResult};
pub use notification::{MainButton, Notification, NotificationResponse};
pub use notification::{MainButton, Notification, NotificationResponse, Sound};
use objc_foundation::{INSDictionary, INSString, NSString};
use std::ops::Deref;
use std::sync::Once;
Expand Down
79 changes: 50 additions & 29 deletions src/notification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use objc_foundation::{INSDictionary, INSString, NSDictionary, NSString};
use objc_id::Id;
use std::default::Default;
use std::ops::Deref;
use std::path::PathBuf;

use crate::error::{NotificationError, NotificationResult};
use crate::{ensure, ensure_application_set, sys};
Expand Down Expand Up @@ -43,6 +42,24 @@ pub enum MainButton<'a> {
Response(&'a str),
}

/// Helper to determine whether you want to play the default sound or custom one
#[derive(Clone)]
pub enum Sound {
/// notification plays the sound [`NSUserNotificationDefaultSoundName`](https://developer.apple.com/documentation/foundation/nsusernotification/nsusernotificationdefaultsoundname)
Default,
/// notification plays your custom sound
Custom(String),
}

impl<I> From<I> for Sound
where
I: ToString,
{
fn from(value: I) -> Self {
Sound::Custom(value.to_string())
}
}

/// Options to further customize the notification
#[derive(Clone, Default)]
pub struct Notification<'a> {
Expand All @@ -54,7 +71,7 @@ pub struct Notification<'a> {
pub(crate) app_icon: Option<&'a str>,
pub(crate) content_image: Option<&'a str>,
pub(crate) delivery_date: Option<f64>,
pub(crate) sound: Option<&'a str>,
pub(crate) sound: Option<Sound>,
pub(crate) asynchronous: Option<bool>,
}

Expand Down Expand Up @@ -156,29 +173,46 @@ impl<'a> Notification<'a> {
self
}

/// Play a system sound when the notification is delivered
/// Play the default sound `"NSUserNotificationDefaultSoundName"` system sound when the notification is delivered.
/// # Example:
///
/// ```no_run
/// # use mac_notification_sys::*;
/// let _ = Notification::new().sound("Blow");
/// ```
pub fn default_sound(&mut self) -> &mut Self {
self.sound = Some(Sound::Default);
self
}

/// Play a system sound when the notification is delivered. Use [`Sound::Default`] to play the default sound.
/// # Example:
///
/// ```no_run
/// # use mac_notification_sys::*;
/// let _ = Notification::new().sound("Blow");
/// ```
pub fn sound(&mut self, sound: &'a str) -> &mut Self {
self.sound = Some(sound);
pub fn sound<S>(&mut self, sound: S) -> &mut Self
where
S: Into<Sound>,
{
self.sound = Some(sound.into());
self
}

/// Play a system sound when the notification is delivered
/// Play a system sound when the notification is delivered. Use [`Sound::Default`] to play the default sound.
///
/// # Example:
///
/// ```no_run
/// # use mac_notification_sys::*;
/// let _ = Notification::new().sound("Blow");
/// ```
pub fn maybe_sound(&mut self, sound: Option<&'a str>) -> &mut Self {
self.sound = sound;
pub fn maybe_sound<S>(&mut self, sound: Option<S>) -> &mut Self
where
S: Into<Sound>,
{
self.sound = sound.map(Into::into);
self
}

Expand Down Expand Up @@ -210,6 +244,7 @@ impl<'a> Notification<'a> {
&*NSString::from_str("deliveryDate"),
&*NSString::from_str("asynchronous"),
&*NSString::from_str("sound"),
&*NSString::from_str("soundIsCustom"),
];
let (main_button_label, actions, is_response): (&str, &[&str], bool) =
match &self.main_button {
Expand All @@ -223,6 +258,12 @@ impl<'a> Notification<'a> {
None => ("", &[], false),
};

let sound = match self.sound {
Some(Sound::Custom(ref name)) => name.as_str(),
Some(Sound::Default) => "NSUserNotificationDefaultSoundName",
None => "",
};

let vals = vec![
NSString::from_str(main_button_label),
// TODO: Find a way to support NSArray as a NSDictionary Value rather than JUST NSString so I don't have to convert array to string and back
Expand All @@ -241,10 +282,7 @@ impl<'a> Notification<'a> {
Some(true) => "yes",
_ => "no",
}),
NSString::from_str(match self.sound {
Some(sound) if check_sound(sound) => sound,
_ => "_mute",
}),
NSString::from_str(sound),
];
NSDictionary::from_keys_and_objects(keys, vals)
}
Expand Down Expand Up @@ -335,20 +373,3 @@ impl NotificationResponse {
}
}
}

pub(crate) fn check_sound(sound_name: &str) -> bool {
dirs_next::home_dir()
.map(|path| path.join("/Library/Sounds/"))
.into_iter()
.chain(
[
"/Library/Sounds/",
"/Network/Library/Sounds/",
"/System/Library/Sounds/",
]
.iter()
.map(PathBuf::from),
)
.map(|sound_path| sound_path.join(format!("{}.aiff", sound_name)))
.any(|some_path| some_path.exists())
}

0 comments on commit c0ffee2

Please sign in to comment.