blob: 733785418f6e0fd4090f2c5b82517d01982ab8a5 [file] [log] [blame]
[email protected]0996e9b2011-08-26 17:59:011// Copyright (c) 2011 The Chromium Authors. All rights reserved.
[email protected]3641da6c2009-07-08 14:59:062// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/global_keyboard_shortcuts_mac.h"
6
jackhou06c25a92015-07-30 03:11:187#import <AppKit/AppKit.h>
Erik Chen8f2f95682018-06-08 00:44:548#include <Carbon/Carbon.h>
jackhou06c25a92015-07-30 03:11:189
[email protected]92f5adfa2010-01-05 09:49:1210#include "base/logging.h"
erikchen22be2092018-06-12 15:54:0011#include "base/mac/foundation_util.h"
Elly Fong-Jones58ccd172018-03-22 17:40:5112#include "base/no_destructor.h"
Erik Chen8f2f95682018-06-08 00:44:5413#include "base/stl_util.h"
Elly Fong-Jones58ccd172018-03-22 17:40:5114#include "build/buildflag.h"
[email protected]1a3aba82010-11-08 23:52:5415#include "chrome/app/chrome_command_ids.h"
erikchen22be2092018-06-12 15:54:0016#import "chrome/browser/app_controller_mac.h"
Erik Chen8f2f95682018-06-08 00:44:5417#include "chrome/browser/ui/cocoa/accelerators_cocoa.h"
andresantoso2389f842015-03-31 23:26:3618#import "chrome/browser/ui/cocoa/nsmenuitem_additions.h"
Erik Chen8f2f95682018-06-08 00:44:5419#include "ui/base/accelerators/accelerator.h"
erikchen008ab23a2018-08-09 02:06:3920#include "ui/base/accelerators/platform_accelerator_cocoa.h"
Erik Chen8f2f95682018-06-08 00:44:5421#include "ui/events/event_constants.h"
22#include "ui/events/keycodes/keyboard_code_conversion_mac.h"
andresantoso2389f842015-03-31 23:26:3623
24namespace {
25
erikchen008ab23a2018-08-09 02:06:3926// Returns a ui::Accelerator given a KeyboardShortcutData.
27ui::Accelerator AcceleratorFromShortcut(const KeyboardShortcutData& shortcut) {
28 int modifiers = 0;
29 if (shortcut.command_key)
30 modifiers |= ui::EF_COMMAND_DOWN;
31 if (shortcut.shift_key)
32 modifiers |= ui::EF_SHIFT_DOWN;
33 if (shortcut.cntrl_key)
34 modifiers |= ui::EF_CONTROL_DOWN;
35 if (shortcut.opt_key)
36 modifiers |= ui::EF_ALT_DOWN;
37
38 return ui::Accelerator(ui::KeyboardCodeFromKeyCode(shortcut.vkey_code),
39 modifiers);
40}
41
andresantoso2389f842015-03-31 23:26:3642// Returns the menu item associated with |key| in |menu|, or nil if not found.
43NSMenuItem* FindMenuItem(NSEvent* key, NSMenu* menu) {
44 NSMenuItem* result = nil;
45
46 for (NSMenuItem* item in [menu itemArray]) {
47 NSMenu* submenu = [item submenu];
48 if (submenu) {
49 if (submenu != [NSApp servicesMenu])
50 result = FindMenuItem(key, submenu);
Erik Chen8f2f95682018-06-08 00:44:5451 } else if ([item cr_firesForKeyEvent:key]) {
andresantoso2389f842015-03-31 23:26:3652 result = item;
53 }
54
55 if (result)
56 break;
57 }
58
59 return result;
60}
61
Erik Chen8f2f95682018-06-08 00:44:5462int MenuCommandForKeyEvent(NSEvent* event) {
63 if ([event type] != NSKeyDown)
64 return -1;
65
erikchen22be2092018-06-12 15:54:0066 // We avoid calling -[NSMenuDelegate menuNeedsUpdate:] on each submenu's
67 // delegate as that can be slow. Instead, we update the relevant NSMenuItems
68 // if [NSApp delegate] is an instance of AppController. See
69 // https://crbug.com/851260#c4.
70 [base::mac::ObjCCast<AppController>([NSApp delegate])
71 updateMenuItemKeyEquivalents];
72
73 // Then call -[NSMenu update], which will validate every user interface item.
74 [[NSApp mainMenu] update];
75
Erik Chen8f2f95682018-06-08 00:44:5476 NSMenuItem* item = FindMenuItem(event, [NSApp mainMenu]);
77
78 if (!item)
79 return -1;
80
81 if ([item action] == @selector(commandDispatch:) && [item tag] > 0)
82 return [item tag];
83
84 // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items
85 // that do not correspond to IDC_ constants need no special treatment however,
86 // as they can't be blacklisted in
87 // |BrowserCommandController::IsReservedCommandOrKey()| anyhow.
88 if ([item action] == @selector(performClose:))
89 return IDC_CLOSE_WINDOW;
90
91 // "Exit" doesn't use the |commandDispatch:| mechanism either.
92 if ([item action] == @selector(terminate:))
93 return IDC_EXIT;
94
95 return -1;
96}
97
mblshacb9c6b9d2016-11-21 17:11:1898bool MatchesEventForKeyboardShortcut(const KeyboardShortcutData& shortcut,
99 bool command_key,
100 bool shift_key,
101 bool cntrl_key,
102 bool opt_key,
Erik Chene4ab96f2018-06-09 04:21:11103 int vkey_code) {
104 return shortcut.command_key == command_key &&
105 shortcut.shift_key == shift_key && shortcut.cntrl_key == cntrl_key &&
106 shortcut.opt_key == opt_key && shortcut.vkey_code == vkey_code;
[email protected]92f5adfa2010-01-05 09:49:12107}
108
erikchen4c9d0842018-06-14 17:04:08109const std::vector<KeyboardShortcutData>&
110GetDelayedShortcutsNotPresentInMainMenu() {
111 static base::NoDestructor<std::vector<KeyboardShortcutData>> keys({
112 // cmd shift cntrl option vkeycode command
113 //--- ----- ----- ------ -------- -------
114 {true, false, false, false, kVK_LeftArrow, IDC_BACK},
115 {true, false, false, false, kVK_RightArrow, IDC_FORWARD},
116 });
117 return *keys;
118}
119
erikchen41281cd2018-06-20 18:15:05120CommandForKeyEventResult NoCommand() {
121 return {-1, /*from_main_menu=*/false};
122}
123
124CommandForKeyEventResult MainMenuCommand(int cmd) {
125 return {cmd, /*from_main_menu=*/true};
126}
127
128CommandForKeyEventResult ShortcutCommand(int cmd) {
129 return {cmd, /*from_main_menu=*/false};
130}
131
mblshacb9c6b9d2016-11-21 17:11:18132} // namespace
133
Erik Chen8f2f95682018-06-08 00:44:54134const std::vector<KeyboardShortcutData>& GetShortcutsNotPresentInMainMenu() {
Elly Fong-Jones58ccd172018-03-22 17:40:51135 // clang-format off
Erik Chen8f2f95682018-06-08 00:44:54136 static base::NoDestructor<std::vector<KeyboardShortcutData>> keys({
Erik Chene4ab96f2018-06-09 04:21:11137 //cmd shift cntrl option vkeycode command
138 //--- ----- ----- ------ -------- -------
139 {true, true, false, false, kVK_ANSI_RightBracket, IDC_SELECT_NEXT_TAB},
140 {true, true, false, false, kVK_ANSI_LeftBracket, IDC_SELECT_PREVIOUS_TAB},
141 {false, false, true, false, kVK_PageDown, IDC_SELECT_NEXT_TAB},
Erik Chene4ab96f2018-06-09 04:21:11142 {false, false, true, false, kVK_PageUp, IDC_SELECT_PREVIOUS_TAB},
erikchen3e0fccc2018-07-12 17:52:19143 {true, false, false, true, kVK_RightArrow, IDC_SELECT_NEXT_TAB},
144 {true, false, false, true, kVK_LeftArrow, IDC_SELECT_PREVIOUS_TAB},
Elly Fong-Jones58ccd172018-03-22 17:40:51145
Elly Fong-Jones58ccd172018-03-22 17:40:51146 // Cmd-0..8 select the nth tab, with cmd-9 being "last tab".
Erik Chene4ab96f2018-06-09 04:21:11147 {true, false, false, false, kVK_ANSI_1, IDC_SELECT_TAB_0},
148 {true, false, false, false, kVK_ANSI_Keypad1, IDC_SELECT_TAB_0},
149 {true, false, false, false, kVK_ANSI_2, IDC_SELECT_TAB_1},
150 {true, false, false, false, kVK_ANSI_Keypad2, IDC_SELECT_TAB_1},
151 {true, false, false, false, kVK_ANSI_3, IDC_SELECT_TAB_2},
152 {true, false, false, false, kVK_ANSI_Keypad3, IDC_SELECT_TAB_2},
153 {true, false, false, false, kVK_ANSI_4, IDC_SELECT_TAB_3},
154 {true, false, false, false, kVK_ANSI_Keypad4, IDC_SELECT_TAB_3},
155 {true, false, false, false, kVK_ANSI_5, IDC_SELECT_TAB_4},
156 {true, false, false, false, kVK_ANSI_Keypad5, IDC_SELECT_TAB_4},
157 {true, false, false, false, kVK_ANSI_6, IDC_SELECT_TAB_5},
158 {true, false, false, false, kVK_ANSI_Keypad6, IDC_SELECT_TAB_5},
159 {true, false, false, false, kVK_ANSI_7, IDC_SELECT_TAB_6},
160 {true, false, false, false, kVK_ANSI_Keypad7, IDC_SELECT_TAB_6},
161 {true, false, false, false, kVK_ANSI_8, IDC_SELECT_TAB_7},
162 {true, false, false, false, kVK_ANSI_Keypad8, IDC_SELECT_TAB_7},
163 {true, false, false, false, kVK_ANSI_9, IDC_SELECT_LAST_TAB},
164 {true, false, false, false, kVK_ANSI_Keypad9, IDC_SELECT_LAST_TAB},
165 {true, true, false, false, kVK_ANSI_M, IDC_SHOW_AVATAR_MENU},
166 {true, false, false, true, kVK_ANSI_L, IDC_SHOW_DOWNLOADS},
Erik Chene4ab96f2018-06-09 04:21:11167 {true, true, false, false, kVK_ANSI_C, IDC_DEV_TOOLS_INSPECT},
erikchen7ee15a502018-09-06 22:06:29168 {true, false, false, true, kVK_ANSI_C, IDC_DEV_TOOLS_INSPECT},
Elly Fong-Jones58ccd172018-03-22 17:40:51169 });
170 // clang-format on
Erik Chen8f2f95682018-06-08 00:44:54171 return *keys;
[email protected]92f5adfa2010-01-05 09:49:12172}
173
erikchen008ab23a2018-08-09 02:06:39174const std::vector<NSMenuItem*>& GetMenuItemsNotPresentInMainMenu() {
175 static base::NoDestructor<std::vector<NSMenuItem*>> menu_items;
176 if (menu_items->empty()) {
177 for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) {
178 ui::Accelerator accelerator = AcceleratorFromShortcut(shortcut);
179 NSString* key_equivalent = nil;
180 NSUInteger modifier_mask = 0;
181 ui::GetKeyEquivalentAndModifierMaskFromAccelerator(
182 accelerator, &key_equivalent, &modifier_mask);
183
184 // Intentionally leaked!
185 NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:@""
186 action:NULL
187 keyEquivalent:key_equivalent];
188 item.keyEquivalentModifierMask = modifier_mask;
189
190 // We store the command in the tag.
191 item.tag = shortcut.chrome_command;
192 menu_items->push_back(item);
193 }
194 }
195 return *menu_items;
196}
197
erikchen41281cd2018-06-20 18:15:05198CommandForKeyEventResult CommandForKeyEvent(NSEvent* event) {
Erik Chen8f2f95682018-06-08 00:44:54199 DCHECK(event);
andresantoso2389f842015-03-31 23:26:36200 if ([event type] != NSKeyDown)
erikchen41281cd2018-06-20 18:15:05201 return NoCommand();
andresantoso2389f842015-03-31 23:26:36202
Elly Fong-Jones1320aeb72017-10-13 20:30:37203 int cmdNum = MenuCommandForKeyEvent(event);
204 if (cmdNum != -1)
erikchen41281cd2018-06-20 18:15:05205 return MainMenuCommand(cmdNum);
andresantoso2389f842015-03-31 23:26:36206
Erik Chen8f2f95682018-06-08 00:44:54207 // Scan through keycodes and see if it corresponds to one of the non-menu
208 // shortcuts.
erikchen008ab23a2018-08-09 02:06:39209 for (NSMenuItem* menu_item : GetMenuItemsNotPresentInMainMenu()) {
210 if ([menu_item cr_firesForKeyEvent:event])
211 return ShortcutCommand(menu_item.tag);
[email protected]92f5adfa2010-01-05 09:49:12212 }
213
erikchen41281cd2018-06-20 18:15:05214 return NoCommand();
Erik Chen8f2f95682018-06-08 00:44:54215}
216
erikchen4c9d0842018-06-14 17:04:08217int DelayedWebContentsCommandForKeyEvent(NSEvent* event) {
218 DCHECK(event);
219 if ([event type] != NSKeyDown)
220 return -1;
221
222 // Look in secondary keyboard shortcuts.
223 NSUInteger modifiers = [event modifierFlags];
224 const bool cmdKey = (modifiers & NSCommandKeyMask) != 0;
225 const bool shiftKey = (modifiers & NSShiftKeyMask) != 0;
226 const bool cntrlKey = (modifiers & NSControlKeyMask) != 0;
227 const bool optKey = (modifiers & NSAlternateKeyMask) != 0;
228 const int keyCode = [event keyCode];
229
230 // Scan through keycodes and see if it corresponds to one of the non-menu
231 // shortcuts.
232 for (const auto& shortcut : GetDelayedShortcutsNotPresentInMainMenu()) {
233 if (MatchesEventForKeyboardShortcut(shortcut, cmdKey, shiftKey, cntrlKey,
234 optKey, keyCode)) {
235 return shortcut.chrome_command;
236 }
237 }
238
239 return -1;
240}
241
Erik Chen8f2f95682018-06-08 00:44:54242// AppKit sends an event via performKeyEquivalent: if it has at least one of the
243// command or control modifiers, and is an NSKeyDown event. CommandDispatcher
244// supplements this by also sending event with the option modifier to
245// performKeyEquivalent:.
246bool EventUsesPerformKeyEquivalent(NSEvent* event) {
247 NSUInteger modifiers = [event modifierFlags];
248 if ((modifiers & (NSEventModifierFlagCommand | NSEventModifierFlagControl |
249 NSEventModifierFlagOption)) == 0) {
250 return false;
251 }
252 return [event type] == NSKeyDown;
253}
254
255bool GetDefaultMacAcceleratorForCommandId(int command_id,
256 ui::Accelerator* accelerator) {
257 // See if it corresponds to one of the non-menu shortcuts.
258 for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) {
Erik Chene4ab96f2018-06-09 04:21:11259 if (shortcut.chrome_command == command_id) {
erikchen008ab23a2018-08-09 02:06:39260 *accelerator = AcceleratorFromShortcut(shortcut);
Erik Chen8f2f95682018-06-08 00:44:54261 return true;
262 }
263 }
264
265 // See if it corresponds to one of the default NSMenu keyEquivalents.
266 const ui::Accelerator* default_nsmenu_equivalent =
267 AcceleratorsCocoa::GetInstance()->GetAcceleratorForCommand(command_id);
268 if (default_nsmenu_equivalent)
269 *accelerator = *default_nsmenu_equivalent;
270 return default_nsmenu_equivalent != nullptr;
[email protected]1d313b832009-10-09 01:26:20271}