1 // Copyright 2011 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/global_keyboard_shortcuts_mac.h"
7 #import <AppKit/AppKit.h>
8 #include <Carbon/Carbon.h>
10 #include "base/apple/foundation_util.h"
11 #include "base/check.h"
12 #include "base/feature_list.h"
13 #include "base/no_destructor.h"
14 #include "build/buildflag.h"
15 #include "chrome/app/chrome_command_ids.h"
16 #import "chrome/browser/app_controller_mac.h"
17 #include "chrome/browser/ui/cocoa/accelerators_cocoa.h"
18 #include "chrome/browser/ui/ui_features.h"
19 #include "ui/base/accelerators/accelerator.h"
20 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
21 #import "ui/base/cocoa/nsmenu_additions.h"
22 #import "ui/base/cocoa/nsmenuitem_additions.h"
23 #include "ui/base/ui_base_features.h"
24 #include "ui/events/event_constants.h"
25 #include "ui/events/keycodes/keyboard_code_conversion_mac.h"
29 // Returns a ui::Accelerator given a KeyboardShortcutData.
30 ui::Accelerator AcceleratorFromShortcut(const KeyboardShortcutData& shortcut) {
32 if (shortcut.command_key)
33 modifiers |= ui::EF_COMMAND_DOWN;
34 if (shortcut.shift_key)
35 modifiers |= ui::EF_SHIFT_DOWN;
36 if (shortcut.cntrl_key)
37 modifiers |= ui::EF_CONTROL_DOWN;
39 modifiers |= ui::EF_ALT_DOWN;
41 return ui::Accelerator(ui::KeyboardCodeFromKeyCode(shortcut.vkey_code),
45 int MenuCommandForKeyEvent(NSEvent* event) {
46 NSMenuItem* item = [[NSApp mainMenu] cr_menuItemForKeyEquivalentEvent:event];
51 if ([item action] == @selector(commandDispatch:) && [item tag] > 0)
54 // "Close window", "Quit", and other commands don't use the `commandDispatch:`
55 // mechanism. Menu items that do not correspond to IDC_ constants need no
56 // special treatment however, as they can't be reserved in
57 // |BrowserCommandController::IsReservedCommandOrKey()| anyhow.
58 SEL itemAction = [item action];
60 if (itemAction == @selector(performClose:))
61 return IDC_CLOSE_WINDOW;
63 if (itemAction == @selector(terminate:))
69 bool MatchesEventForKeyboardShortcut(const KeyboardShortcutData& shortcut,
75 return shortcut.command_key == command_key &&
76 shortcut.shift_key == shift_key && shortcut.cntrl_key == cntrl_key &&
77 shortcut.opt_key == opt_key && shortcut.vkey_code == vkey_code;
80 const std::vector<KeyboardShortcutData>&
81 GetDelayedShortcutsNotPresentInMainMenu() {
83 static base::NoDestructor<std::vector<KeyboardShortcutData>> keys({
84 // cmd shift cntrl option vkeycode command
85 // --- ----- ----- ------ -------- -------
86 {true, false, false, false, kVK_LeftArrow, IDC_BACK},
87 {true, false, false, false, kVK_RightArrow, IDC_FORWARD},
93 CommandForKeyEventResult NoCommand() {
94 return {NO_COMMAND, /*from_main_menu=*/false};
97 CommandForKeyEventResult MainMenuCommand(int cmd) {
98 return {cmd, /*from_main_menu=*/true};
101 CommandForKeyEventResult ShortcutCommand(int cmd) {
102 return {cmd, /*from_main_menu=*/false};
107 // Returns a vector of hidden keyboard shortcuts (i.e. ones that arent present
108 // in the menus). Note that the hidden "Cmd =" shortcut is somehow enabled by
109 // the ui::VKEY_OEM_PLUS entry in accelerators_cocoa.mm.
110 const std::vector<KeyboardShortcutData>& GetShortcutsNotPresentInMainMenu() {
111 static const base::NoDestructor<std::vector<KeyboardShortcutData>> keys([]() {
113 std::vector<KeyboardShortcutData> keys({
114 // cmd shift cntrl option vkeycode command
115 // --- ----- ----- ------ -------- -------
116 {true, true, false, false, kVK_ANSI_RightBracket, IDC_SELECT_NEXT_TAB},
117 {true, true, false, false, kVK_ANSI_LeftBracket, IDC_SELECT_PREVIOUS_TAB},
118 {false, false, true, false, kVK_PageDown, IDC_SELECT_NEXT_TAB},
119 {false, false, true, false, kVK_PageUp, IDC_SELECT_PREVIOUS_TAB},
120 {true, false, false, true, kVK_RightArrow, IDC_SELECT_NEXT_TAB},
121 {true, false, false, true, kVK_LeftArrow, IDC_SELECT_PREVIOUS_TAB},
122 {false, true, true, false, kVK_PageDown, IDC_MOVE_TAB_NEXT},
123 {false, true, true, false, kVK_PageUp, IDC_MOVE_TAB_PREVIOUS},
125 // Cmd-0..8 select the nth tab, with cmd-9 being "last tab".
126 {true, false, false, false, kVK_ANSI_1, IDC_SELECT_TAB_0},
127 {true, false, false, false, kVK_ANSI_Keypad1, IDC_SELECT_TAB_0},
128 {true, false, false, false, kVK_ANSI_2, IDC_SELECT_TAB_1},
129 {true, false, false, false, kVK_ANSI_Keypad2, IDC_SELECT_TAB_1},
130 {true, false, false, false, kVK_ANSI_3, IDC_SELECT_TAB_2},
131 {true, false, false, false, kVK_ANSI_Keypad3, IDC_SELECT_TAB_2},
132 {true, false, false, false, kVK_ANSI_4, IDC_SELECT_TAB_3},
133 {true, false, false, false, kVK_ANSI_Keypad4, IDC_SELECT_TAB_3},
134 {true, false, false, false, kVK_ANSI_5, IDC_SELECT_TAB_4},
135 {true, false, false, false, kVK_ANSI_Keypad5, IDC_SELECT_TAB_4},
136 {true, false, false, false, kVK_ANSI_6, IDC_SELECT_TAB_5},
137 {true, false, false, false, kVK_ANSI_Keypad6, IDC_SELECT_TAB_5},
138 {true, false, false, false, kVK_ANSI_7, IDC_SELECT_TAB_6},
139 {true, false, false, false, kVK_ANSI_Keypad7, IDC_SELECT_TAB_6},
140 {true, false, false, false, kVK_ANSI_8, IDC_SELECT_TAB_7},
141 {true, false, false, false, kVK_ANSI_Keypad8, IDC_SELECT_TAB_7},
142 {true, false, false, false, kVK_ANSI_9, IDC_SELECT_LAST_TAB},
143 {true, false, false, false, kVK_ANSI_Keypad9, IDC_SELECT_LAST_TAB},
145 {true, true, false, false, kVK_ANSI_M, IDC_SHOW_AVATAR_MENU},
146 {true, false, false, true, kVK_ANSI_L, IDC_SHOW_DOWNLOADS},
147 {true, true, false, false, kVK_ANSI_C, IDC_DEV_TOOLS_INSPECT},
148 {true, false, false, true, kVK_ANSI_C, IDC_DEV_TOOLS_INSPECT},
149 {true, false, false, true, kVK_DownArrow, IDC_FOCUS_NEXT_PANE},
150 {true, false, false, true, kVK_UpArrow, IDC_FOCUS_PREVIOUS_PANE},
151 {true, true, false, true, kVK_ANSI_A, IDC_FOCUS_INACTIVE_POPUP_FOR_ACCESSIBILITY},
155 if (base::FeatureList::IsEnabled(features::kUIDebugTools)) {
157 {false, true, true, true, kVK_ANSI_T, IDC_DEBUG_TOGGLE_TABLET_MODE});
159 {false, true, true, true, kVK_ANSI_V, IDC_DEBUG_PRINT_VIEW_TREE});
160 keys.push_back({false, true, true, true, kVK_ANSI_M,
161 IDC_DEBUG_PRINT_VIEW_TREE_DETAILS});
168 const std::vector<NSMenuItem*>& GetMenuItemsNotPresentInMainMenu() {
169 static base::NoDestructor<std::vector<NSMenuItem*>> menu_items([]() {
170 std::vector<NSMenuItem*> menu_items;
171 for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) {
172 ui::Accelerator accelerator = AcceleratorFromShortcut(shortcut);
173 KeyEquivalentAndModifierMask* equivalent =
174 ui::GetKeyEquivalentAndModifierMaskFromAccelerator(accelerator);
176 // Intentionally leaked!
178 [[NSMenuItem alloc] initWithTitle:@""
180 keyEquivalent:equivalent.keyEquivalent];
181 item.keyEquivalentModifierMask = equivalent.modifierMask;
183 // We store the command in the tag.
184 item.tag = shortcut.chrome_command;
185 menu_items.push_back(item);
192 CommandForKeyEventResult CommandForKeyEvent(NSEvent* event) {
194 if ([event type] != NSEventTypeKeyDown)
197 int cmdNum = MenuCommandForKeyEvent(event);
198 if (cmdNum != NO_COMMAND)
199 return MainMenuCommand(cmdNum);
201 // Scan through keycodes and see if it corresponds to one of the non-menu
203 for (NSMenuItem* menu_item : GetMenuItemsNotPresentInMainMenu()) {
204 if ([menu_item cr_firesForKeyEquivalentEvent:event])
205 return ShortcutCommand(menu_item.tag);
211 int DelayedWebContentsCommandForKeyEvent(NSEvent* event) {
213 if ([event type] != NSEventTypeKeyDown)
216 // Look in secondary keyboard shortcuts.
217 NSUInteger modifiers = [event modifierFlags];
218 const bool cmdKey = (modifiers & NSEventModifierFlagCommand) != 0;
219 const bool shiftKey = (modifiers & NSEventModifierFlagShift) != 0;
220 const bool cntrlKey = (modifiers & NSEventModifierFlagControl) != 0;
221 const bool optKey = (modifiers & NSEventModifierFlagOption) != 0;
222 const int keyCode = [event keyCode];
224 // Scan through keycodes and see if it corresponds to one of the non-menu
226 for (const auto& shortcut : GetDelayedShortcutsNotPresentInMainMenu()) {
227 if (MatchesEventForKeyboardShortcut(shortcut, cmdKey, shiftKey, cntrlKey,
229 return shortcut.chrome_command;
236 // AppKit sends an event via performKeyEquivalent: if it has at least one of the
237 // command or control modifiers, and is an NSEventTypeKeyDown event.
238 // CommandDispatcher supplements this by also sending event with the option
239 // modifier to performKeyEquivalent:.
240 bool EventUsesPerformKeyEquivalent(NSEvent* event) {
241 return ([event modifierFlags] & ui::cocoa::ModifierMaskForKeyEvent(event)) !=
245 bool GetDefaultMacAcceleratorForCommandId(int command_id,
246 ui::Accelerator* accelerator) {
247 // See if it corresponds to one of the non-menu shortcuts.
248 for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) {
249 if (shortcut.chrome_command == command_id) {
250 *accelerator = AcceleratorFromShortcut(shortcut);
255 // See if it corresponds to one of the default NSMenu keyEquivalents.
256 const ui::Accelerator* default_nsmenu_equivalent =
257 AcceleratorsCocoa::GetInstance()->GetAcceleratorForCommand(command_id);
258 if (default_nsmenu_equivalent)
259 *accelerator = *default_nsmenu_equivalent;
260 return default_nsmenu_equivalent != nullptr;