diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 78c84310da31..963cf28fc0af 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -40,6 +40,7 @@ void Menu::AfterInit(v8::Isolate* isolate) { delegate.Get("isCommandIdChecked", &is_checked_); delegate.Get("isCommandIdEnabled", &is_enabled_); delegate.Get("isCommandIdVisible", &is_visible_); + delegate.Get("shouldCommandIdWorkWhenHidden", &works_when_hidden_); delegate.Get("getAcceleratorForCommandId", &get_accelerator_); delegate.Get("shouldRegisterAcceleratorForCommandId", &should_register_accelerator_); @@ -65,6 +66,12 @@ bool Menu::IsCommandIdVisible(int command_id) const { return is_visible_.Run(GetWrapper(), command_id); } +bool Menu::ShouldCommandIdWorkWhenHidden(int command_id) const { + v8::Locker locker(isolate()); + v8::HandleScope handle_scope(isolate()); + return works_when_hidden_.Run(GetWrapper(), command_id); +} + bool Menu::GetAcceleratorForCommandIdWithParams( int command_id, bool use_default_accelerator, @@ -181,6 +188,10 @@ bool Menu::IsVisibleAt(int index) const { return model_->IsVisibleAt(index); } +bool Menu::WorksWhenHiddenAt(int index) const { + return model_->WorksWhenHiddenAt(index); +} + void Menu::OnMenuWillClose() { Emit("menu-will-close"); } @@ -212,6 +223,7 @@ void Menu::BuildPrototype(v8::Isolate* isolate, .SetMethod("getAcceleratorTextAt", &Menu::GetAcceleratorTextAt) .SetMethod("isItemCheckedAt", &Menu::IsItemCheckedAt) .SetMethod("isEnabledAt", &Menu::IsEnabledAt) + .SetMethod("worksWhenHiddenAt", &Menu::WorksWhenHiddenAt) .SetMethod("isVisibleAt", &Menu::IsVisibleAt) .SetMethod("popupAt", &Menu::PopupAt) .SetMethod("closePopupAt", &Menu::ClosePopupAt); diff --git a/atom/browser/api/atom_api_menu.h b/atom/browser/api/atom_api_menu.h index 69738ff46ba2..528c39c36854 100644 --- a/atom/browser/api/atom_api_menu.h +++ b/atom/browser/api/atom_api_menu.h @@ -47,6 +47,7 @@ class Menu : public mate::TrackableObject, bool IsCommandIdChecked(int command_id) const override; bool IsCommandIdEnabled(int command_id) const override; bool IsCommandIdVisible(int command_id) const override; + bool ShouldCommandIdWorkWhenHidden(int command_id) const override; bool GetAcceleratorForCommandIdWithParams( int command_id, bool use_default_accelerator, @@ -96,11 +97,13 @@ class Menu : public mate::TrackableObject, bool IsItemCheckedAt(int index) const; bool IsEnabledAt(int index) const; bool IsVisibleAt(int index) const; + bool WorksWhenHiddenAt(int index) const; // Stored delegate methods. base::Callback, int)> is_checked_; base::Callback, int)> is_enabled_; base::Callback, int)> is_visible_; + base::Callback, int)> works_when_hidden_; base::Callback(v8::Local, int, bool)> get_accelerator_; base::Callback, int)> should_register_accelerator_; diff --git a/atom/browser/mac/atom_application.h b/atom/browser/mac/atom_application.h index 0905caa41b24..ad513dadb3e5 100644 --- a/atom/browser/mac/atom_application.h +++ b/atom/browser/mac/atom_application.h @@ -46,6 +46,13 @@ typedef NS_ENUM(NSInteger, AVAuthorizationStatusMac) { AVAuthorizationStatusAuthorizedMac = 3, }; +@interface NSMenuItem (HighSierraSDK) +@property(atomic, readwrite) + BOOL allowsKeyEquivalentWhenHidden API_AVAILABLE(macosx(10.13)); +- (void)setAllowsKeyEquivalentWhenHidden:(BOOL)arg1 + API_AVAILABLE(macosx(10.13)); +@end + @interface AVCaptureDevice (MojaveSDK) + (void)requestAccessForMediaType:(AVMediaType)mediaType completionHandler:(void (^)(BOOL granted))handler diff --git a/atom/browser/ui/atom_menu_model.cc b/atom/browser/ui/atom_menu_model.cc index d50b85acb7db..714fa60ce40f 100644 --- a/atom/browser/ui/atom_menu_model.cc +++ b/atom/browser/ui/atom_menu_model.cc @@ -51,6 +51,13 @@ bool AtomMenuModel::ShouldRegisterAcceleratorAt(int index) const { return true; } +bool AtomMenuModel::WorksWhenHiddenAt(int index) const { + if (delegate_) { + return delegate_->ShouldCommandIdWorkWhenHidden(GetCommandIdAt(index)); + } + return true; +} + void AtomMenuModel::MenuWillClose() { ui::SimpleMenuModel::MenuWillClose(); for (Observer& observer : observers_) { diff --git a/atom/browser/ui/atom_menu_model.h b/atom/browser/ui/atom_menu_model.h index 34636793a077..42d2e80d7542 100644 --- a/atom/browser/ui/atom_menu_model.h +++ b/atom/browser/ui/atom_menu_model.h @@ -27,6 +27,8 @@ class AtomMenuModel : public ui::SimpleMenuModel { virtual bool ShouldRegisterAcceleratorForCommandId( int command_id) const = 0; + virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0; + private: // ui::SimpleMenuModel::Delegate: bool GetAcceleratorForCommandId( @@ -57,6 +59,7 @@ class AtomMenuModel : public ui::SimpleMenuModel { bool use_default_accelerator, ui::Accelerator* accelerator) const; bool ShouldRegisterAcceleratorAt(int index) const; + bool WorksWhenHiddenAt(int index) const; // ui::SimpleMenuModel: void MenuWillClose() override; diff --git a/atom/browser/ui/cocoa/atom_menu_controller.mm b/atom/browser/ui/cocoa/atom_menu_controller.mm index e28504a26a60..d76950f0ba16 100644 --- a/atom/browser/ui/cocoa/atom_menu_controller.mm +++ b/atom/browser/ui/cocoa/atom_menu_controller.mm @@ -5,6 +5,7 @@ #import "atom/browser/ui/cocoa/atom_menu_controller.h" +#include "atom/browser/mac/atom_application.h" #include "atom/browser/ui/atom_menu_model.h" #include "base/logging.h" #include "base/strings/sys_string_conversions.h" @@ -291,6 +292,11 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; [item setKeyEquivalentModifierMask:modifier_mask]; } + if (@available(macOS 10.13, *)) { + [(id)item + setAllowsKeyEquivalentWhenHidden:(model->WorksWhenHiddenAt(index))]; + } + // Set menu item's role. [item setTarget:self]; if (!role.empty()) { @@ -307,8 +313,7 @@ static base::scoped_nsobject recentDocumentsMenuSwap_; } // Called before the menu is to be displayed to update the state (enabled, -// radio, etc) of each item in the menu. Also will update the title if -// the item is marked as "dynamic". +// radio, etc) of each item in the menu. - (BOOL)validateUserInterfaceItem:(id)item { SEL action = [item action]; if (action != @selector(itemSelected:)) diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 194b9be0015d..45ab83d7d5c7 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -24,6 +24,7 @@ See [`Menu`](menu.md) for examples. * `icon` ([NativeImage](native-image.md) | String) (optional) * `enabled` Boolean (optional) - If false, the menu item will be greyed out and unclickable. + * `acceleratorWorksWhenHidden` Boolean (optional) - default is `true`, and when `false` will prevent the accelerator from triggering the item if the item is not visible`. _macOS_ * `visible` Boolean (optional) - If false, the menu item will be entirely hidden. * `checked` Boolean (optional) - Should only be specified for `checkbox` or `radio` type menu items. @@ -48,6 +49,8 @@ See [`Menu`](menu.md) for examples. the placement of their containing group after the containing group of the item with the specified label. +**Note:** `acceleratorWorksWhenHidden` is specified as being macOS-only because accelerators always work when items are hidden on Windows and Linux. The option is exposed to users to give them the option to turn it off, as this is possible in native macOS development. This property is only usable on macOS High Sierra 10.13 or newer. + ### Roles Roles allow menu items to have predefined behaviors. diff --git a/lib/browser/api/menu-item.js b/lib/browser/api/menu-item.js index 33388cbb6d40..93c929cf9b01 100644 --- a/lib/browser/api/menu-item.js +++ b/lib/browser/api/menu-item.js @@ -36,6 +36,7 @@ const MenuItem = function (options) { this.overrideProperty('enabled', true) this.overrideProperty('visible', true) this.overrideProperty('checked', false) + this.overrideProperty('acceleratorWorksWhenHidden', true) this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role)) if (!MenuItem.types.includes(this.type)) { diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 04e3b173a95d..c8e25b5e6e47 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -17,6 +17,7 @@ Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype) const delegate = { isCommandIdChecked: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].checked : undefined, isCommandIdEnabled: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].enabled : undefined, + shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined, isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined, getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => { const command = menu.commandsMap[id]