blob: eb7d6414976333f8da1dbee5465452d4bece82e3 [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>
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
mblshacb9c6b9d2016-11-21 17:11:1836bool MatchesEventForKeyboardShortcut(const KeyboardShortcutData& shortcut,
37 bool command_key,
38 bool shift_key,
39 bool cntrl_key,
40 bool opt_key,
41 int vkey_code,
42 unichar key_char) {
[email protected]92f5adfa2010-01-05 09:49:1243 // Expects that one of |key_char| or |vkey_code| is 0.
44 DCHECK((shortcut.key_char == 0) ^ (shortcut.vkey_code == 0));
45 if (shortcut.key_char) {
ellyjonesc2af9512016-09-27 14:36:5946 // Shortcuts that have a |key_char| and have |opt_key| set are mistakes,
47 // since |opt_key| is not checked when there is a |key_char|.
48 DCHECK(!shortcut.opt_key);
[email protected]92f5adfa2010-01-05 09:49:1249 // The given shortcut key is to be matched by a keyboard character.
50 // In this case we ignore shift and opt (alt) key modifiers, because
51 // the character may be generated by a combination with those keys.
52 if (shortcut.command_key == command_key &&
53 shortcut.cntrl_key == cntrl_key &&
54 shortcut.key_char == key_char)
55 return true;
56 } else if (shortcut.vkey_code) {
57 // The given shortcut key is to be matched by a virtual key code.
58 if (shortcut.command_key == command_key &&
59 shortcut.shift_key == shift_key &&
60 shortcut.cntrl_key == cntrl_key &&
61 shortcut.opt_key == opt_key &&
62 shortcut.vkey_code == vkey_code)
63 return true;
64 } else {
65 NOTREACHED(); // Shouldn't happen.
66 }
67 return false;
68}
69
mblshacb9c6b9d2016-11-21 17:11:1870int CommandForKeyboardShortcut(const std::vector<KeyboardShortcutData>& table,
71 bool command_key,
72 bool shift_key,
73 bool cntrl_key,
74 bool opt_key,
75 int vkey_code,
76 unichar key_char) {
[email protected]3641da6c2009-07-08 14:59:0677 // Scan through keycodes and see if it corresponds to one of the global
78 // shortcuts on file.
79 //
80 // TODO(jeremy): Change this into a hash table once we get enough
81 // entries in the array to make a difference.
[email protected]92f5adfa2010-01-05 09:49:1282 // (When turning this into a hash table, note that the current behavior
83 // relies on the order of the table (see the comment for '{' / '}' above).
mblshacb9c6b9d2016-11-21 17:11:1884 for (const auto& shortcut : table) {
85 if (MatchesEventForKeyboardShortcut(shortcut, command_key, shift_key,
86 cntrl_key, opt_key, vkey_code,
87 key_char))
88 return shortcut.chrome_command;
[email protected]3641da6c2009-07-08 14:59:0689 }
90
91 return -1;
92}
[email protected]1d313b832009-10-09 01:26:2093
mblshacb9c6b9d2016-11-21 17:11:1894} // namespace
95
[email protected]1d313b832009-10-09 01:26:2096int CommandForWindowKeyboardShortcut(
[email protected]f7378a32009-10-21 17:15:2897 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:1298 int vkey_code, unichar key_char) {
mblshacb9c6b9d2016-11-21 17:11:1899 return CommandForKeyboardShortcut(GetWindowKeyboardShortcutTable(),
[email protected]1d313b832009-10-09 01:26:20100 command_key, shift_key,
[email protected]92f5adfa2010-01-05 09:49:12101 cntrl_key, opt_key, vkey_code,
102 key_char);
[email protected]1d313b832009-10-09 01:26:20103}
104
[email protected]1ade7b62009-12-18 08:56:18105int CommandForDelayedWindowKeyboardShortcut(
106 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:12107 int vkey_code, unichar key_char) {
mblshacb9c6b9d2016-11-21 17:11:18108 return CommandForKeyboardShortcut(GetDelayedWindowKeyboardShortcutTable(),
[email protected]1ade7b62009-12-18 08:56:18109 command_key, shift_key,
[email protected]92f5adfa2010-01-05 09:49:12110 cntrl_key, opt_key, vkey_code,
111 key_char);
[email protected]1ade7b62009-12-18 08:56:18112}
113
[email protected]1d313b832009-10-09 01:26:20114int CommandForBrowserKeyboardShortcut(
[email protected]f7378a32009-10-21 17:15:28115 bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
[email protected]92f5adfa2010-01-05 09:49:12116 int vkey_code, unichar key_char) {
mblshacb9c6b9d2016-11-21 17:11:18117 return CommandForKeyboardShortcut(GetBrowserKeyboardShortcutTable(),
[email protected]1d313b832009-10-09 01:26:20118 command_key, shift_key,
[email protected]92f5adfa2010-01-05 09:49:12119 cntrl_key, opt_key, vkey_code,
120 key_char);
121}
122
andresantoso2389f842015-03-31 23:26:36123int CommandForKeyEvent(NSEvent* event) {
124 if ([event type] != NSKeyDown)
125 return -1;
126
127 // Look in menu.
128 NSMenuItem* item = FindMenuItem(event, [NSApp mainMenu]);
129 if (item && [item action] == @selector(commandDispatch:) && [item tag] > 0)
130 return [item tag];
131
132 // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items
133 // that do not correspond to IDC_ constants need no special treatment however,
134 // as they can't be blacklisted in
135 // |BrowserCommandController::IsReservedCommandOrKey()| anyhow.
136 if (item && [item action] == @selector(performClose:))
137 return IDC_CLOSE_WINDOW;
138
139 // "Exit" doesn't use the |commandDispatch:| mechanism either.
140 if (item && [item action] == @selector(terminate:))
141 return IDC_EXIT;
142
143 // Look in secondary keyboard shortcuts.
144 NSUInteger modifiers = [event modifierFlags];
145 const bool cmdKey = (modifiers & NSCommandKeyMask) != 0;
146 const bool shiftKey = (modifiers & NSShiftKeyMask) != 0;
147 const bool cntrlKey = (modifiers & NSControlKeyMask) != 0;
148 const bool optKey = (modifiers & NSAlternateKeyMask) != 0;
149 const int keyCode = [event keyCode];
150 const unichar keyChar = KeyCharacterForEvent(event);
151
152 int cmdNum = CommandForWindowKeyboardShortcut(
153 cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
154 if (cmdNum != -1)
155 return cmdNum;
156
157 cmdNum = CommandForBrowserKeyboardShortcut(
158 cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
159 if (cmdNum != -1)
160 return cmdNum;
161
162 return -1;
163}
164
[email protected]92f5adfa2010-01-05 09:49:12165unichar KeyCharacterForEvent(NSEvent* event) {
[email protected]b881f4f42010-11-16 08:58:27166 NSString* eventString = [event charactersIgnoringModifiers];
167 NSString* characters = [event characters];
168
[email protected]92f5adfa2010-01-05 09:49:12169 if ([eventString length] != 1)
170 return 0;
171
172 if ([characters length] != 1)
173 return [eventString characterAtIndex:0];
174
[email protected]147f1242013-12-02 21:52:34175 // Some characters are BiDi mirrored. The mirroring is different
176 // for different OS versions. Instead of having a mirror table, map
177 // raw/processed pairs to desired outputs.
178 const struct {
179 unichar rawChar;
180 unichar unmodChar;
181 unichar targetChar;
182 } kCharMapping[] = {
183 // OSX 10.8 mirrors certain chars.
184 {'{', '}', '{'},
185 {'}', '{', '}'},
186 {'(', ')', '('},
187 {')', '(', ')'},
188
189 // OSX 10.9 has the unshifted raw char.
190 {'[', '}', '{'},
191 {']', '{', '}'},
192 {'9', ')', '('},
193 {'0', '(', ')'},
194
195 // These are the same either way.
196 {'[', ']', '['},
197 {']', '[', ']'},
198 };
199
[email protected]b881f4f42010-11-16 08:58:27200 unichar noModifiersChar = [eventString characterAtIndex:0];
201 unichar rawChar = [characters characterAtIndex:0];
[email protected]147f1242013-12-02 21:52:34202
203 // Only apply transformation table for ascii.
[email protected]b881f4f42010-11-16 08:58:27204 if (isascii(noModifiersChar) && isascii(rawChar)) {
[email protected]147f1242013-12-02 21:52:34205 // Alphabetic characters aren't mirrored, go with the raw character.
206 // [A previous partial comment said something about Dvorak?]
[email protected]b881f4f42010-11-16 08:58:27207 if (isalpha(rawChar))
208 return rawChar;
209
210 // http://crbug.com/42517
[email protected]147f1242013-12-02 21:52:34211 // http://crbug.com/315379
[email protected]b881f4f42010-11-16 08:58:27212 // In RTL keyboard layouts, Cocoa mirrors characters in the string
213 // returned by [event charactersIgnoringModifiers]. In this case, return
214 // the raw (unmirrored) char.
viettrungluu9e65ad12014-10-16 04:22:26215 for (size_t i = 0; i < arraysize(kCharMapping); ++i) {
[email protected]147f1242013-12-02 21:52:34216 if (rawChar == kCharMapping[i].rawChar &&
217 noModifiersChar == kCharMapping[i].unmodChar) {
218 return kCharMapping[i].targetChar;
219 }
[email protected]b881f4f42010-11-16 08:58:27220 }
221
[email protected]92f5adfa2010-01-05 09:49:12222 // opt/alt modifier is set (e.g. on german layout we want '{' for opt-8).
223 if ([event modifierFlags] & NSAlternateKeyMask)
[email protected]147f1242013-12-02 21:52:34224 return rawChar;
[email protected]92f5adfa2010-01-05 09:49:12225 }
226
[email protected]147f1242013-12-02 21:52:34227 return noModifiersChar;
[email protected]1d313b832009-10-09 01:26:20228}