diff --git a/.clang-format b/.clang-format index 421a880..f5d97e4 100644 --- a/.clang-format +++ b/.clang-format @@ -2,10 +2,30 @@ BasedOnStyle: LLVM UseTab: Never IndentWidth: 4 TabWidth: 4 -BreakBeforeBraces: Allman + +AccessModifierOffset: -2 +AlignTrailingComments: false AllowShortIfStatementsOnASingleLine: false -IndentCaseLabels: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakBeforeTernaryOperators: true ColumnLimit: 0 -AccessModifierOffset: -4 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +DerivePointerAlignment: false +IndentCaseLabels: true PointerAlignment: Left -ConstructorInitializerAllOnOneLineOrOnePerLine: true \ No newline at end of file +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: false +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false \ No newline at end of file diff --git a/build.rs b/build.rs index e498f50..8ab23d7 100644 --- a/build.rs +++ b/build.rs @@ -25,5 +25,7 @@ fn main() { .compile("notify"); println!("cargo:rerun-if-env-changed={}", DEPLOYMENT_TARGET_VAR); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=objc"); } } diff --git a/examples/actions.rs b/examples/actions.rs index edfa35f..f1e5c14 100644 --- a/examples/actions.rs +++ b/examples/actions.rs @@ -1,24 +1,50 @@ use mac_notification_sys::*; fn main() { - let bundle = get_bundle_identifier_or_default("firefox"); - set_application(&bundle).unwrap(); let response = send_notification( - "Danger", - Some("Will Robinson"), - "Run away as fast as you can", + "main button with drop down", + None, + "choose wisely", + Some(Notification::new().main_button(MainButton::DropdownActions( + "Dropdown", + &["Action 1", "Action 2"], + ))), + ) + .unwrap(); + handle_repsonse(response); + + let response = send_notification( + "take response", + None, + "type what you want", + Some(Notification::new().main_button(MainButton::Response(r#"you want "foobar""#))), + ) + .unwrap(); + handle_repsonse(response); + + let response = send_notification( + "Single Action", + None, + "ok?", + Some(Notification::new().main_button(MainButton::SingleAction("Ok"))), + ) + .unwrap(); + handle_repsonse(response); + + let response = send_notification( + "close button only", + None, + "close it well", Some( - Notification::new() - .main_button(MainButton::DropdownActions( - "Dropdown", - &["Action 1", "Action 2"], - )) - .close_button("Nevermind..."), + Notification::new().close_button("Nevermind..."), ), ) .unwrap(); + handle_repsonse(response); +} - match response { +fn handle_repsonse(response: NotificationResponse) { + match dbg!(response) { // Requires main_button to be a MainButton::SingleAction or MainButton::DropdownActions NotificationResponse::ActionButton(action_name) => { if action_name == "Action 1" { diff --git a/examples/two_simple.rs b/examples/two_simple.rs new file mode 100644 index 0000000..27f7948 --- /dev/null +++ b/examples/two_simple.rs @@ -0,0 +1,28 @@ +use mac_notification_sys::*; + +fn main() { + let bundle = get_bundle_identifier_or_default("firefox"); + println!("{}", bundle); + + set_application(&bundle).unwrap(); + + Notification::default() + .title("Danger") + .subtitle("Will Robinson") + .message("Run away as fast as you can") + .send() + .unwrap(); + + Notification::default() + .title("two") + .message("two") + .send() + .unwrap(); + + Notification::default() + .title("NOW") + .message("Without subtitle") + .sound("Submarine") + .send() + .unwrap(); +} diff --git a/objc/notify.h b/objc/notify.h index 3e6ffff..aca25c4 100644 --- a/objc/notify.h +++ b/objc/notify.h @@ -6,24 +6,18 @@ NSString* fakeBundleIdentifier = nil; @implementation NSBundle (swizzle) -- (NSString*)__bundleIdentifier -{ - if (self == [NSBundle mainBundle]) - { +- (NSString*)__bundleIdentifier { + if (self == [NSBundle mainBundle]) { return fakeBundleIdentifier ? fakeBundleIdentifier : @"com.apple.Terminal"; - } - else - { + } else { return [self __bundleIdentifier]; } } @end -BOOL installNSBundleHook() -{ +BOOL installNSBundleHook() { Class class = objc_getClass("NSBundle"); - if (class) - { + if (class) { method_exchangeImplementations(class_getInstanceMethod(class, @selector(bundleIdentifier)), class_getInstanceMethod(class, @selector(__bundleIdentifier))); return YES; @@ -31,7 +25,7 @@ BOOL installNSBundleHook() return NO; } -@interface NotificationCenterDelegate : NSObject +@interface NotificationCenterDelegate: NSObject @property(nonatomic, assign) BOOL keepRunning; @property(nonatomic, retain) NSDictionary* actionData; @end @@ -39,58 +33,54 @@ BOOL installNSBundleHook() // Delegate to respond to events in the NSUserNotificationCenter // See https://developer.apple.com/documentation/foundation/nsusernotificationcenterdelegate?language=objc @implementation NotificationCenterDelegate -- (void)userNotificationCenter:(NSUserNotificationCenter*)center didDeliverNotification:(NSUserNotification*)notification -{ +- (void)userNotificationCenter:(NSUserNotificationCenter*)center + didDeliverNotification:(NSUserNotification*)notification { // Stop running if we're not expecting a response - if (!notification.hasActionButton && !notification.hasReplyButton) - { + if (!notification.hasActionButton && !notification.hasReplyButton) { self.keepRunning = NO; } } // Most typical actions -- (void)userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification*)notification -{ - unsigned long long additionalActionIndex = ULLONG_MAX; - NSString* ActionsClicked = @""; +- (void)userNotificationCenter:(NSUserNotificationCenter*)center + didActivateNotification:(NSUserNotification*)notification { + long long additionalActionIndex = ULONG_MAX; // Switch on how the notification was interacted with // See https://developer.apple.com/documentation/foundation/nsusernotification/1416143-activationtype?language=objc - switch (notification.activationType) - { + switch (notification.activationType) { case NSUserNotificationActivationTypeActionButtonClicked: - case NSUserNotificationActivationTypeAdditionalActionClicked: - { - if ([[(NSObject*)notification valueForKey:@"_alternateActionButtonTitles"] count] > 1) - { + case NSUserNotificationActivationTypeAdditionalActionClicked: { + if ([[(NSObject*)notification valueForKey:@"_alternateActionButtonTitles"] count] > 1) { NSNumber* alternateActionIndex = [(NSObject*)notification valueForKey:@"_alternateActionIndex"]; additionalActionIndex = [alternateActionIndex unsignedLongLongValue]; - ActionsClicked = [(NSObject*)notification valueForKey:@"_alternateActionButtonTitles"][additionalActionIndex]; - self.actionData = @{@"activationType" : @"actionClicked", @"activationValue" : ActionsClicked, @"activationValueIndex" : [NSString stringWithFormat:@"%llu", additionalActionIndex]}; - } - else - { - self.actionData = @{@"activationType" : @"actionClicked", @"activationValue" : notification.actionButtonTitle}; + if (additionalActionIndex == LONG_MAX) { + self.actionData = @{@"activationType": @"actionClicked", @"activationValue": notification.actionButtonTitle}; + break; + } + + NSString* ActionsClicked = [(NSObject*)notification valueForKey:@"_alternateActionButtonTitles"][additionalActionIndex]; + + self.actionData = @{@"activationType": @"actionClicked", @"activationValue": ActionsClicked, @"activationValueIndex": [NSString stringWithFormat:@"%llu", additionalActionIndex]}; + } else { + self.actionData = @{@"activationType": @"actionClicked", @"activationValue": notification.actionButtonTitle}; } break; } - case NSUserNotificationActivationTypeContentsClicked: - { - self.actionData = @{@"activationType" : @"contentsClicked"}; + case NSUserNotificationActivationTypeContentsClicked: { + self.actionData = @{@"activationType": @"contentsClicked"}; break; } - case NSUserNotificationActivationTypeReplied: - { - self.actionData = @{@"activationType" : @"replied", @"activationValue" : notification.response.string}; + case NSUserNotificationActivationTypeReplied: { + self.actionData = @{@"activationType": @"replied", @"activationValue": notification.response.string}; break; } case NSUserNotificationActivationTypeNone: - default: - { - self.actionData = @{@"activationType" : @"none"}; + default: { + self.actionData = @{@"activationType": @"none"}; break; } } @@ -103,9 +93,9 @@ BOOL installNSBundleHook() } // Specific to the close/other button -- (void)userNotificationCenter:(NSUserNotificationCenter*)center didDismissAlert:(NSUserNotification*)notification -{ - self.actionData = @{@"activationType" : @"closeClicked", @"activationValue" : notification.otherButtonTitle}; +- (void)userNotificationCenter:(NSUserNotificationCenter*)center + didDismissAlert:(NSUserNotification*)notification { + self.actionData = @{@"activationType": @"closeClicked", @"activationValue": notification.otherButtonTitle}; // Stop running after interacting with the notification self.keepRunning = NO; @@ -116,11 +106,9 @@ BOOL installNSBundleHook() @end // Utility function to create an NSImage from an url -NSImage* getImageFromURL(NSString* url) -{ +NSImage* getImageFromURL(NSString* url) { NSURL* imageURL = [NSURL URLWithString:url]; - if ([[imageURL scheme] length] == 0) - { + if ([[imageURL scheme] length] == 0) { // Prefix 'file://' if no scheme imageURL = [NSURL fileURLWithPath:url]; } diff --git a/objc/notify.m b/objc/notify.m index cdd45db..5f9db29 100644 --- a/objc/notify.m +++ b/objc/notify.m @@ -1,8 +1,7 @@ #import "notify.h" // getBundleIdentifier(app_name: &str) -> "com.apple.Terminal" -NSString* getBundleIdentifier(NSString* appName) -{ +NSString* getBundleIdentifier(NSString* appName) { NSString* findString = [NSString stringWithFormat:@"get id of application \"%@\"", appName]; NSAppleScript* findScript = [[NSAppleScript alloc] initWithSource:findString]; NSAppleEventDescriptor* resultDescriptor = [findScript executeAndReturnError:nil]; @@ -11,16 +10,12 @@ // setApplication(new_bundle_identifier: &str) -> Result<()> // invariant: this function should be called at most once and before `sendNotification` -BOOL setApplication(NSString* newbundleIdentifier) -{ - @autoreleasepool - { - if (!installNSBundleHook()) - { +BOOL setApplication(NSString* newbundleIdentifier) { + @autoreleasepool { + if (!installNSBundleHook()) { return NO; } - if (LSCopyApplicationURLsForBundleIdentifier((CFStringRef)newbundleIdentifier, NULL) != NULL) - { + if (LSCopyApplicationURLsForBundleIdentifier((CFStringRef)newbundleIdentifier, NULL) != NULL) { [fakeBundleIdentifier release]; // Release old value - nil is ok fakeBundleIdentifier = newbundleIdentifier; [newbundleIdentifier retain]; // Retain new value - it outlives this scope @@ -32,10 +27,8 @@ BOOL setApplication(NSString* newbundleIdentifier) } // sendNotification(title: &str, subtitle: &str, message: &str, options: Notification) -> NotificationResult<()> -NSDictionary* sendNotification(NSString* title, NSString* subtitle, NSString* message, NSDictionary* options) -{ - @autoreleasepool - { +NSDictionary* sendNotification(NSString* title, NSString* subtitle, NSString* message, NSDictionary* options) { + @autoreleasepool { // For a list of available notification options, see https://developer.apple.com/documentation/foundation/nsusernotification?language=objc NSUserNotificationCenter* notificationCenter = [NSUserNotificationCenter defaultUserNotificationCenter]; @@ -51,28 +44,22 @@ BOOL setApplication(NSString* newbundleIdentifier) // Basic text userNotification.title = title; - if (![subtitle isEqualToString:@""]) - { + if (![subtitle isEqualToString:@""]) { userNotification.subtitle = subtitle; } userNotification.informativeText = message; // Notification sound - if (options[@"sound"] && ![options[@"sound"] isEqualToString:@""]) - { - if ([options[@"sound"] isEqualToString:@"NSUserNotificationDefaultSoundName"]) - { + if (options[@"sound"] && ![options[@"sound"] isEqualToString:@""]) { + if ([options[@"sound"] isEqualToString:@"NSUserNotificationDefaultSoundName"]) { userNotification.soundName = NSUserNotificationDefaultSoundName; - } - else - { + } else { userNotification.soundName = options[@"sound"]; } } // Delivery Date/Schedule - if (options[@"deliveryDate"] && ![options[@"deliveryDate"] isEqualToString:@""]) - { + if (options[@"deliveryDate"] && ![options[@"deliveryDate"] isEqualToString:@""]) { ncDelegate.keepRunning = YES; double deliveryDate = [options[@"deliveryDate"] doubleValue]; NSDate* scheduleTime = [NSDate dateWithTimeIntervalSince1970:deliveryDate]; @@ -82,71 +69,60 @@ BOOL setApplication(NSString* newbundleIdentifier) } // Main Actions Button (defaults to "Show") - if (options[@"mainButtonLabel"] && ![options[@"mainButtonLabel"] isEqualToString:@""]) - { + if (options[@"mainButtonLabel"] && ![options[@"mainButtonLabel"] isEqualToString:@""]) { ncDelegate.keepRunning = YES; userNotification.actionButtonTitle = options[@"mainButtonLabel"]; userNotification.hasActionButton = 1; } // Dropdown actions - if (options[@"actions"] && ![options[@"actions"] isEqualToString:@""]) - { + if (options[@"actions"] && ![options[@"actions"] isEqualToString:@""]) { ncDelegate.keepRunning = YES; [userNotification setValue:@YES forKey:@"_showsButtons"]; NSArray* myActions = [options[@"actions"] componentsSeparatedByString:@","]; - if (myActions.count > 1) - { + if (myActions.count > 1) { [userNotification setValue:@YES forKey:@"_alwaysShowAlternateActionMenu"]; [userNotification setValue:myActions forKey:@"_alternateActionButtonTitles"]; } } // Close/Other button (defaults to "Cancel") - if (options[@"closeButtonLabel"] && ![options[@"closeButtonLabel"] isEqualToString:@""]) - { + if (options[@"closeButtonLabel"] && ![options[@"closeButtonLabel"] isEqualToString:@""]) { ncDelegate.keepRunning = YES; [userNotification setValue:@YES forKey:@"_showsButtons"]; userNotification.otherButtonTitle = options[@"closeButtonLabel"]; } // Reply to the notification with a text field - if (options[@"response"] && ![options[@"response"] isEqualToString:@""]) - { + if (options[@"response"] && ![options[@"response"] isEqualToString:@""]) { ncDelegate.keepRunning = YES; userNotification.hasReplyButton = 1; userNotification.responsePlaceholder = options[@"mainButtonLabel"]; } // Change the icon of the app in the notification - if (options[@"appIcon"] && ![options[@"appIcon"] isEqualToString:@""]) - { + if (options[@"appIcon"] && ![options[@"appIcon"] isEqualToString:@""]) { NSImage* icon = getImageFromURL(options[@"appIcon"]); // replacement app icon [userNotification setValue:icon forKey:@"_identityImage"]; [userNotification setValue:@(false) forKey:@"_identityImageHasBorder"]; } // Change the additional content image - if (options[@"contentImage"] && ![options[@"contentImage"] isEqualToString:@""]) - { + if (options[@"contentImage"] && ![options[@"contentImage"] isEqualToString:@""]) { userNotification.contentImage = getImageFromURL(options[@"contentImage"]); } // If set to asynchronous, do not wait for actions - if (options[@"asynchronous"] && [options[@"asynchronous"] isEqualToString:@"yes"]) - { + if (options[@"asynchronous"] && [options[@"asynchronous"] isEqualToString:@"yes"]) { ncDelegate.keepRunning = NO; } // Send or schedule notification - if (isScheduled) - { + if (isScheduled) { [notificationCenter scheduleNotification:userNotification]; - } - else - { + } else { [notificationCenter deliverNotification:userNotification]; } @@ -154,8 +130,7 @@ BOOL setApplication(NSString* newbundleIdentifier) // TODO: Issue #4 mentions an issue with multithreading, perhaps there could be an overall "synchronous" option (instead of deliveryDate's synchronous section) // Loop/wait for a user action if needed - while (ncDelegate.keepRunning) - { + while (ncDelegate.keepRunning) { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; }