blob: d5ba13821419c8107394d0d16a21997616a2d4c5 [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
[email protected]3641da6c2009-07-08 14:59:065#include "chrome/browser/global_keyboard_shortcuts_mac.h"
6
jackhou06c25a92015-07-30 03:11:187#import <AppKit/AppKit.h>
8
[email protected]92f5adfa2010-01-05 09:49:129#include "base/logging.h"
avi6846aef2015-12-26 01:09:3810#include "base/macros.h"
[email protected]1a3aba82010-11-08 23:52:5411#include "chrome/app/chrome_command_ids.h"
andresantoso2389f842015-03-31 23:26:3612#import "chrome/browser/ui/cocoa/nsmenuitem_additions.h"
13
14namespace {
15
16// Returns the menu item associated with |key| in |menu|, or nil if not found.
17NSMenuItem* FindMenuItem(NSEvent* key, NSMenu* menu) {
18 NSMenuItem* result = nil;
19
20 for (NSMenuItem* item in [menu itemArray]) {
21 NSMenu* submenu = [item submenu];
22 if (submenu) {
23 if (submenu != [NSApp servicesMenu])
24 result = FindMenuItem(key, submenu);
25 } else if ([item cr_firesForKeyEventIfEnabled:key]) {
26 result = item;
27 }
28
29 if (result)
30 break;
31 }
32
33 return result;
34}
35
36} // namespace
[email protected]3641da6c2009-07-08 14:59:0637
[email protected]1ade7b62009-12-18 08:56:1838// Basically, there are two kinds of keyboard shortcuts: Ones that should work
39// only if the tab contents is focused (BrowserKeyboardShortcut), and ones that
40// should work in all other cases (WindowKeyboardShortcut). In the latter case,
41// we differentiate between shortcuts that are checked before any other view
42// gets the chance to handle them (WindowKeyboardShortcut) or after all views
43// had a chance but did not handle the keypress event
[email protected]8656ff1f2010-11-19 22:18:1244// (DelayedWindowKeyboardShortcut).
[email protected]1ade7b62009-12-18 08:56:1845
[email protected]8656ff1f2010-11-19 22:18:1246const KeyboardShortcutData* GetWindowKeyboardShortcutTable(
47 size_t* num_entries) {
[email protected]3641da6c2009-07-08 14:59:0648 static const KeyboardShortcutData keyboard_shortcuts[] = {
[email protected]8656ff1f2010-11-19 22:18:1249 //cmd shift cntrl option
50 //--- ----- ----- ------
[email protected]92f5adfa2010-01-05 09:49:1251 // '{' / '}' characters should be matched earlier than virtual key code
52 // (therefore we can match alt-8 as '{' on german keyboards).
53 {true, false, false, false, 0, '}', IDC_SELECT_NEXT_TAB},
54 {true, false, false, false, 0, '{', IDC_SELECT_PREVIOUS_TAB},
55 {false, false, true, false, kVK_PageDown, 0, IDC_SELECT_NEXT_TAB},
56 {false, false, true, false, kVK_Tab, 0, IDC_SELECT_NEXT_TAB},
57 {false, false, true, false, kVK_PageUp, 0, IDC_SELECT_PREVIOUS_TAB},
58 {false, true, true, false, kVK_Tab, 0, IDC_SELECT_PREVIOUS_TAB},
[email protected]b6b0ca42009-09-18 16:24:2459 // Cmd-0..8 select the Nth tab, with cmd-9 being "last tab".
[email protected]b7b0bcb2010-11-17 17:12:2460 {true, false, false, false, kVK_ANSI_1, 0, IDC_SELECT_TAB_0},
61 {true, false, false, false, kVK_ANSI_Keypad1, 0, IDC_SELECT_TAB_0},
62 {true, false, false, false, kVK_ANSI_2, 0, IDC_SELECT_TAB_1},
63 {true, false, false, false, kVK_ANSI_Keypad2, 0, IDC_SELECT_TAB_1},
64 {true, false, false, false, kVK_ANSI_3, 0, IDC_SELECT_TAB_2},
65 {true, false, false, false, kVK_ANSI_Keypad3, 0, IDC_SELECT_TAB_2},
66 {true, false, false, false, kVK_ANSI_4, 0, IDC_SELECT_TAB_3},
67 {true, false, false, false, kVK_ANSI_Keypad4, 0, IDC_SELECT_TAB_3},
68 {true, false, false, false, kVK_ANSI_5, 0, IDC_SELECT_TAB_4},
69 {true, false, false, false, kVK_ANSI_Keypad5, 0, IDC_SELECT_TAB_4},
70 {true, false, false, false, kVK_ANSI_6, 0, IDC_SELECT_TAB_5},
71 {true, false, false, false, kVK_ANSI_Keypad6, 0, IDC_SELECT_TAB_5},
72 {true, false, false, false, kVK_ANSI_7, 0, IDC_SELECT_TAB_6},
73 {true, false, false, false, kVK_ANSI_Keypad7, 0, IDC_SELECT_TAB_6},
74 {true, false, false, false, kVK_ANSI_8, 0, IDC_SELECT_TAB_7},
75 {true, false, false, false, kVK_ANSI_Keypad8, 0, IDC_SELECT_TAB_7},
76 {true, false, false, false, kVK_ANSI_9, 0, IDC_SELECT_LAST_TAB},
77 {true, false, false, false, kVK_ANSI_Keypad9, 0, IDC_SELECT_LAST_TAB},
[email protected]413d24d2011-11-28 16:16:0578 {true, true, false, false, kVK_ANSI_M, 0, IDC_SHOW_AVATAR_MENU},
[email protected]3641da6c2009-07-08 14:59:0679 };
[email protected]70be00a2009-07-08 23:40:0880
[email protected]3641da6c2009-07-08 14:59:0681 *num_entries = arraysize(keyboard_shortcuts);
[email protected]70be00a2009-07-08 23:40:0882
[email protected]3641da6c2009-07-08 14:59:0683 return keyboard_shortcuts;
84}
85
[email protected]8656ff1f2010-11-19 22:18:1286const KeyboardShortcutData* GetDelayedWindowKeyboardShortcutTable(
87 size_t* num_entries) {
[email protected]1ade7b62009-12-18 08:56:1888 static const KeyboardShortcutData keyboard_shortcuts[] = {
[email protected]8656ff1f2010-11-19 22:18:1289 //cmd shift cntrl option
90 //--- ----- ----- ------
[email protected]92f5adfa2010-01-05 09:49:1291 {false, false, false, false, kVK_Escape, 0, IDC_STOP},
[email protected]1ade7b62009-12-18 08:56:1892 };
93
94 *num_entries = arraysize(keyboard_shortcuts);
95
96 return keyboard_shortcuts;
97}
98
[email protected]8656ff1f2010-11-19 22:18:1299const KeyboardShortcutData* GetBrowserKeyboardShortcutTable(
100 size_t* num_entries) {
[email protected]1d313b832009-10-09 01:26:20101 static const KeyboardShortcutData keyboard_shortcuts[] = {
[email protected]8656ff1f2010-11-19 22:18:12102 //cmd shift cntrl option
103 //--- ----- ----- ------
[email protected]2a8a98122010-07-16 11:58:48104 {true, false, false, false, kVK_LeftArrow, 0, IDC_BACK},
105 {true, false, false, false, kVK_RightArrow, 0, IDC_FORWARD},
ojanc7e48992016-05-20 19:50:18106 {false, false, false, false, kVK_Delete, 0, IDC_BACKSPACE_BACK},
107 {false, true, false, false, kVK_Delete, 0, IDC_BACKSPACE_FORWARD},
[email protected]2a8a98122010-07-16 11:58:48108 {true, true, false, false, 0, 'c', IDC_DEV_TOOLS_INSPECT},
[email protected]1d313b832009-10-09 01:26:20109 };
110
111 *num_entries = arraysize(keyboard_shortcuts);
112
113 return keyboard_shortcuts;
114}
115
[email protected]92f5adfa2010-01-05 09:49:12116static bool MatchesEventForKeyboardShortcut(
117 const KeyboardShortcutData& shortcut,
118 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
119 int vkey_code, unichar key_char) {
120 // Expects that one of |key_char| or |vkey_code| is 0.
121 DCHECK((shortcut.key_char == 0) ^ (shortcut.vkey_code == 0));
122 if (shortcut.key_char) {
123 // The given shortcut key is to be matched by a keyboard character.
124 // In this case we ignore shift and opt (alt) key modifiers, because
125 // the character may be generated by a combination with those keys.
126 if (shortcut.command_key == command_key &&
127 shortcut.cntrl_key == cntrl_key &&
128 shortcut.key_char == key_char)
129 return true;
130 } else if (shortcut.vkey_code) {
131 // The given shortcut key is to be matched by a virtual key code.
132 if (shortcut.command_key == command_key &&
133 shortcut.shift_key == shift_key &&
134 shortcut.cntrl_key == cntrl_key &&
135 shortcut.opt_key == opt_key &&
136 shortcut.vkey_code == vkey_code)
137 return true;
138 } else {
139 NOTREACHED(); // Shouldn't happen.
140 }
141 return false;
142}
143
[email protected]1d313b832009-10-09 01:26:20144static int CommandForKeyboardShortcut(
145 const KeyboardShortcutData* (*get_keyboard_shortcut_table)(size_t*),
[email protected]f7378a32009-10-21 17:15:28146 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:12147 int vkey_code, unichar key_char) {
[email protected]3641da6c2009-07-08 14:59:06148
149 // Scan through keycodes and see if it corresponds to one of the global
150 // shortcuts on file.
151 //
152 // TODO(jeremy): Change this into a hash table once we get enough
153 // entries in the array to make a difference.
[email protected]92f5adfa2010-01-05 09:49:12154 // (When turning this into a hash table, note that the current behavior
155 // relies on the order of the table (see the comment for '{' / '}' above).
[email protected]3641da6c2009-07-08 14:59:06156 size_t num_shortcuts = 0;
[email protected]1d313b832009-10-09 01:26:20157 const KeyboardShortcutData *it = get_keyboard_shortcut_table(&num_shortcuts);
[email protected]3641da6c2009-07-08 14:59:06158 for (size_t i = 0; i < num_shortcuts; ++i, ++it) {
[email protected]92f5adfa2010-01-05 09:49:12159 if (MatchesEventForKeyboardShortcut(*it, command_key, shift_key, cntrl_key,
160 opt_key, vkey_code, key_char))
[email protected]3641da6c2009-07-08 14:59:06161 return it->chrome_command;
[email protected]3641da6c2009-07-08 14:59:06162 }
163
164 return -1;
165}
[email protected]1d313b832009-10-09 01:26:20166
167int CommandForWindowKeyboardShortcut(
[email protected]f7378a32009-10-21 17:15:28168 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:12169 int vkey_code, unichar key_char) {
[email protected]1d313b832009-10-09 01:26:20170 return CommandForKeyboardShortcut(GetWindowKeyboardShortcutTable,
171 command_key, shift_key,
[email protected]92f5adfa2010-01-05 09:49:12172 cntrl_key, opt_key, vkey_code,
173 key_char);
[email protected]1d313b832009-10-09 01:26:20174}
175
[email protected]1ade7b62009-12-18 08:56:18176int CommandForDelayedWindowKeyboardShortcut(
177 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:12178 int vkey_code, unichar key_char) {
[email protected]1ade7b62009-12-18 08:56:18179 return CommandForKeyboardShortcut(GetDelayedWindowKeyboardShortcutTable,
180 command_key, shift_key,
[email protected]92f5adfa2010-01-05 09:49:12181 cntrl_key, opt_key, vkey_code,
182 key_char);
[email protected]1ade7b62009-12-18 08:56:18183}
184
[email protected]1d313b832009-10-09 01:26:20185int CommandForBrowserKeyboardShortcut(
[email protected]f7378a32009-10-21 17:15:28186 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:12187 int vkey_code, unichar key_char) {
[email protected]1d313b832009-10-09 01:26:20188 return CommandForKeyboardShortcut(GetBrowserKeyboardShortcutTable,
189 command_key, shift_key,
[email protected]92f5adfa2010-01-05 09:49:12190 cntrl_key, opt_key, vkey_code,
191 key_char);
192}
193
andresantoso2389f842015-03-31 23:26:36194int CommandForKeyEvent(NSEvent* event) {
195 if ([event type] != NSKeyDown)
196 return -1;
197
198 // Look in menu.
199 NSMenuItem* item = FindMenuItem(event, [NSApp mainMenu]);
200 if (item && [item action] == @selector(commandDispatch:) && [item tag] > 0)
201 return [item tag];
202
203 // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items
204 // that do not correspond to IDC_ constants need no special treatment however,
205 // as they can't be blacklisted in
206 // |BrowserCommandController::IsReservedCommandOrKey()| anyhow.
207 if (item && [item action] == @selector(performClose:))
208 return IDC_CLOSE_WINDOW;
209
210 // "Exit" doesn't use the |commandDispatch:| mechanism either.
211 if (item && [item action] == @selector(terminate:))
212 return IDC_EXIT;
213
214 // Look in secondary keyboard shortcuts.
215 NSUInteger modifiers = [event modifierFlags];
216 const bool cmdKey = (modifiers & NSCommandKeyMask) != 0;
217 const bool shiftKey = (modifiers & NSShiftKeyMask) != 0;
218 const bool cntrlKey = (modifiers & NSControlKeyMask) != 0;
219 const bool optKey = (modifiers & NSAlternateKeyMask) != 0;
220 const int keyCode = [event keyCode];
221 const unichar keyChar = KeyCharacterForEvent(event);
222
223 int cmdNum = CommandForWindowKeyboardShortcut(
224 cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
225 if (cmdNum != -1)
226 return cmdNum;
227
228 cmdNum = CommandForBrowserKeyboardShortcut(
229 cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
230 if (cmdNum != -1)
231 return cmdNum;
232
233 return -1;
234}
235
[email protected]92f5adfa2010-01-05 09:49:12236unichar KeyCharacterForEvent(NSEvent* event) {
[email protected]b881f4f42010-11-16 08:58:27237 NSString* eventString = [event charactersIgnoringModifiers];
238 NSString* characters = [event characters];
239
[email protected]92f5adfa2010-01-05 09:49:12240 if ([eventString length] != 1)
241 return 0;
242
243 if ([characters length] != 1)
244 return [eventString characterAtIndex:0];
245
[email protected]147f1242013-12-02 21:52:34246 // Some characters are BiDi mirrored. The mirroring is different
247 // for different OS versions. Instead of having a mirror table, map
248 // raw/processed pairs to desired outputs.
249 const struct {
250 unichar rawChar;
251 unichar unmodChar;
252 unichar targetChar;
253 } kCharMapping[] = {
254 // OSX 10.8 mirrors certain chars.
255 {'{', '}', '{'},
256 {'}', '{', '}'},
257 {'(', ')', '('},
258 {')', '(', ')'},
259
260 // OSX 10.9 has the unshifted raw char.
261 {'[', '}', '{'},
262 {']', '{', '}'},
263 {'9', ')', '('},
264 {'0', '(', ')'},
265
266 // These are the same either way.
267 {'[', ']', '['},
268 {']', '[', ']'},
269 };
270
[email protected]b881f4f42010-11-16 08:58:27271 unichar noModifiersChar = [eventString characterAtIndex:0];
272 unichar rawChar = [characters characterAtIndex:0];
[email protected]147f1242013-12-02 21:52:34273
274 // Only apply transformation table for ascii.
[email protected]b881f4f42010-11-16 08:58:27275 if (isascii(noModifiersChar) && isascii(rawChar)) {
[email protected]147f1242013-12-02 21:52:34276 // Alphabetic characters aren't mirrored, go with the raw character.
277 // [A previous partial comment said something about Dvorak?]
[email protected]b881f4f42010-11-16 08:58:27278 if (isalpha(rawChar))
279 return rawChar;
280
281 // http://crbug.com/42517
[email protected]147f1242013-12-02 21:52:34282 // http://crbug.com/315379
[email protected]b881f4f42010-11-16 08:58:27283 // In RTL keyboard layouts, Cocoa mirrors characters in the string
284 // returned by [event charactersIgnoringModifiers]. In this case, return
285 // the raw (unmirrored) char.
viettrungluu9e65ad12014-10-16 04:22:26286 for (size_t i = 0; i < arraysize(kCharMapping); ++i) {
[email protected]147f1242013-12-02 21:52:34287 if (rawChar == kCharMapping[i].rawChar &&
288 noModifiersChar == kCharMapping[i].unmodChar) {
289 return kCharMapping[i].targetChar;
290 }
[email protected]b881f4f42010-11-16 08:58:27291 }
292
[email protected]92f5adfa2010-01-05 09:49:12293 // opt/alt modifier is set (e.g. on german layout we want '{' for opt-8).
294 if ([event modifierFlags] & NSAlternateKeyMask)
[email protected]147f1242013-12-02 21:52:34295 return rawChar;
[email protected]92f5adfa2010-01-05 09:49:12296 }
297
[email protected]147f1242013-12-02 21:52:34298 return noModifiersChar;
[email protected]1d313b832009-10-09 01:26:20299}