diff --git a/brightray/browser/mac/cocoa_notification.h b/brightray/browser/mac/cocoa_notification.h index 9e7b55fabfdd..1cdd7e33b423 100644 --- a/brightray/browser/mac/cocoa_notification.h +++ b/brightray/browser/mac/cocoa_notification.h @@ -7,6 +7,7 @@ #import +#include #include #include @@ -27,7 +28,8 @@ class CocoaNotification : public Notification { void NotificationDisplayed(); void NotificationReplied(const std::string& reply); - void NotificationButtonClicked(); + void NotificationActivated(); + void NotificationActivated(NSUserNotificationAction* action); NSUserNotification* notification() const { return notification_; } @@ -35,7 +37,8 @@ class CocoaNotification : public Notification { void LogAction(const char* action); base::scoped_nsobject notification_; - int action_index_; + std::map additional_action_indices_; + unsigned action_index_; DISALLOW_COPY_AND_ASSIGN(CocoaNotification); }; diff --git a/brightray/browser/mac/cocoa_notification.mm b/brightray/browser/mac/cocoa_notification.mm index 94b8f93fdc3f..7d0786266108 100644 --- a/brightray/browser/mac/cocoa_notification.mm +++ b/brightray/browser/mac/cocoa_notification.mm @@ -29,20 +29,18 @@ CocoaNotification::~CocoaNotification() { void CocoaNotification::Show(const NotificationOptions& options) { notification_.reset([[NSUserNotification alloc] init]); - NSString* identifier = [NSString stringWithFormat:@"%s%d", "ElectronNotification", g_identifier_]; + NSString* identifier = [NSString stringWithFormat:@"ElectronNotification%d", g_identifier_++]; [notification_ setTitle:base::SysUTF16ToNSString(options.title)]; [notification_ setSubtitle:base::SysUTF16ToNSString(options.subtitle)]; [notification_ setInformativeText:base::SysUTF16ToNSString(options.msg)]; [notification_ setIdentifier:identifier]; - g_identifier_++; if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) { LOG(INFO) << "Notification created (" << [identifier UTF8String] << ")"; } - if ([notification_ respondsToSelector:@selector(setContentImage:)] && - !options.icon.drawsNothing()) { + if (!options.icon.drawsNothing()) { NSImage* image = skia::SkBitmapToNSImageWithColorSpace( options.icon, base::mac::GetGenericRGBColorSpace()); [notification_ setContentImage:image]; @@ -50,23 +48,40 @@ void CocoaNotification::Show(const NotificationOptions& options) { if (options.silent) { [notification_ setSoundName:nil]; - } else if (options.sound != nil) { - [notification_ setSoundName:base::SysUTF16ToNSString(options.sound)]; - } else { + } else if (options.sound.empty()) { [notification_ setSoundName:NSUserNotificationDefaultSoundName]; + } else { + [notification_ setSoundName:base::SysUTF16ToNSString(options.sound)]; } [notification_ setHasActionButton:false]; int i = 0; + action_index_ = UINT_MAX; + NSMutableArray* additionalActions = [[[NSMutableArray alloc] init] autorelease]; for (const auto& action : options.actions) { if (action.type == base::ASCIIToUTF16("button")) { - [notification_ setHasActionButton:true]; - [notification_ setActionButtonTitle:base::SysUTF16ToNSString(action.text)]; - action_index_ = i; + if (action_index_ == UINT_MAX) { + // First button observed is the displayed action + [notification_ setHasActionButton:true]; + [notification_ setActionButtonTitle:base::SysUTF16ToNSString(action.text)]; + action_index_ = i; + } else { + // All of the rest are appended to the list of additional actions + NSString* actionIdentifier = [NSString stringWithFormat:@"%@Action%d", identifier, i]; + NSUserNotificationAction* notificationAction = + [NSUserNotificationAction actionWithIdentifier:actionIdentifier + title:base::SysUTF16ToNSString(action.text)]; + [additionalActions addObject:notificationAction]; + additional_action_indices_.insert(std::make_pair(base::SysNSStringToUTF8(actionIdentifier), i)); + } } i++; } + if ([additionalActions count] > 0 && + [notification_ respondsToSelector:@selector(setAdditionalActions:)]) { + [notification_ setAdditionalActions:additionalActions]; // Requires macOS 10.10 + } if (options.has_reply) { [notification_ setResponsePlaceholder:base::SysUTF16ToNSString(options.reply_placeholder)]; @@ -101,13 +116,30 @@ void CocoaNotification::NotificationReplied(const std::string& reply) { this->LogAction("replied to"); } -void CocoaNotification::NotificationButtonClicked() { +void CocoaNotification::NotificationActivated() { if (delegate()) delegate()->NotificationAction(action_index_); this->LogAction("button clicked"); } +void CocoaNotification::NotificationActivated(NSUserNotificationAction* action) { + if (delegate()) { + unsigned index = action_index_; + std::string identifier = base::SysNSStringToUTF8(action.identifier); + for (const auto& it : additional_action_indices_) { + if (it.first == identifier) { + index = it.second; + break; + } + } + + delegate()->NotificationAction(index); + } + + this->LogAction("button clicked"); +} + void CocoaNotification::LogAction(const char* action) { if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) { NSString* identifier = [notification_ valueForKey:@"identifier"]; diff --git a/brightray/browser/mac/notification_center_delegate.mm b/brightray/browser/mac/notification_center_delegate.mm index 6c9fb7f277dd..1cb3dc9a33d3 100644 --- a/brightray/browser/mac/notification_center_delegate.mm +++ b/brightray/browser/mac/notification_center_delegate.mm @@ -34,12 +34,15 @@ } if (notification) { - if (notif.activationType == NSUserNotificationActivationTypeReplied) { - notification->NotificationReplied([notif.response.string UTF8String]); - } else if (notif.activationType == NSUserNotificationActivationTypeActionButtonClicked) { - notification->NotificationButtonClicked(); - } else if (notif.activationType == NSUserNotificationActivationTypeContentsClicked) { + // Ref: https://developer.apple.com/documentation/foundation/nsusernotificationactivationtype?language=objc + if (notif.activationType == NSUserNotificationActivationTypeContentsClicked) { notification->NotificationClicked(); + } else if (notif.activationType == NSUserNotificationActivationTypeActionButtonClicked) { + notification->NotificationActivated(); + } else if (notif.activationType == NSUserNotificationActivationTypeReplied) { + notification->NotificationReplied([notif.response.string UTF8String]); + } else if (notif.activationType == NSUserNotificationActivationTypeAdditionalActionClicked) { + notification->NotificationActivated([notif additionalActivationAction]); } } } diff --git a/docs/api/structures/notification-action.md b/docs/api/structures/notification-action.md index a2cf4e078a91..9817502a8159 100644 --- a/docs/api/structures/notification-action.md +++ b/docs/api/structures/notification-action.md @@ -7,7 +7,7 @@ | Action Type | Platform Support | Usage of `text` | Default `text` | Limitations | |-------------|------------------|-----------------|----------------|-------------| -| `button` | macOS | Used as the label for the button | "Show" | Maximum of one button, if multiple are provided only the last is used. This action is also incompatible with `hasReply` and will be ignored if `hasReply` is `true`. | +| `button` | macOS | Used as the label for the button | "Show" (or a localized string by system default if first of such `button`, otherwise empty) | Only the first one is used. If multiple are provided, those beyond the first will be listed as additional actions (displayed when mouse active over the action button). Any such action also is incompatible with `hasReply` and will be ignored if `hasReply` is `true`. | ### Button support on macOS @@ -15,6 +15,6 @@ In order for extra notification buttons to work on macOS your app must meet the following criteria. * App is signed -* App has it's `NSUserNotificationAlertStyle` set to `alert` in the `info.plist`. +* App has it's `NSUserNotificationAlertStyle` set to `alert` in the `Info.plist`. If either of these requirements are not met the button simply won't appear.