1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <AppKit/NSEvent.h> 6 #include <Carbon/Carbon.h> 7 8 #include "chrome/browser/global_keyboard_shortcuts_mac.h" 9 10 #include "base/basictypes.h" 11 #include "base/logging.h" 12 #include "chrome/app/chrome_command_ids.h" 13 14 // Basically, there are two kinds of keyboard shortcuts: Ones that should work 15 // only if the tab contents is focused (BrowserKeyboardShortcut), and ones that 16 // should work in all other cases (WindowKeyboardShortcut). In the latter case, 17 // we differentiate between shortcuts that are checked before any other view 18 // gets the chance to handle them (WindowKeyboardShortcut) or after all views 19 // had a chance but did not handle the keypress event 20 // (DelayedWindowKeyboardShortcut). 21 22 const KeyboardShortcutData* GetWindowKeyboardShortcutTable( 23 size_t* num_entries) { 24 static const KeyboardShortcutData keyboard_shortcuts[] = { 25 //cmd shift cntrl option 26 //--- ----- ----- ------ 27 // '{' / '}' characters should be matched earlier than virtual key code 28 // (therefore we can match alt-8 as '{' on german keyboards). 29 {true, false, false, false, 0, '}', IDC_SELECT_NEXT_TAB}, 30 {true, false, false, false, 0, '{', IDC_SELECT_PREVIOUS_TAB}, 31 {false, false, true, false, kVK_PageDown, 0, IDC_SELECT_NEXT_TAB}, 32 {false, false, true, false, kVK_Tab, 0, IDC_SELECT_NEXT_TAB}, 33 {false, false, true, false, kVK_PageUp, 0, IDC_SELECT_PREVIOUS_TAB}, 34 {false, true, true, false, kVK_Tab, 0, IDC_SELECT_PREVIOUS_TAB}, 35 // Cmd-0..8 select the Nth tab, with cmd-9 being "last tab". 36 {true, false, false, false, kVK_ANSI_1, 0, IDC_SELECT_TAB_0}, 37 {true, false, false, false, kVK_ANSI_Keypad1, 0, IDC_SELECT_TAB_0}, 38 {true, false, false, false, kVK_ANSI_2, 0, IDC_SELECT_TAB_1}, 39 {true, false, false, false, kVK_ANSI_Keypad2, 0, IDC_SELECT_TAB_1}, 40 {true, false, false, false, kVK_ANSI_3, 0, IDC_SELECT_TAB_2}, 41 {true, false, false, false, kVK_ANSI_Keypad3, 0, IDC_SELECT_TAB_2}, 42 {true, false, false, false, kVK_ANSI_4, 0, IDC_SELECT_TAB_3}, 43 {true, false, false, false, kVK_ANSI_Keypad4, 0, IDC_SELECT_TAB_3}, 44 {true, false, false, false, kVK_ANSI_5, 0, IDC_SELECT_TAB_4}, 45 {true, false, false, false, kVK_ANSI_Keypad5, 0, IDC_SELECT_TAB_4}, 46 {true, false, false, false, kVK_ANSI_6, 0, IDC_SELECT_TAB_5}, 47 {true, false, false, false, kVK_ANSI_Keypad6, 0, IDC_SELECT_TAB_5}, 48 {true, false, false, false, kVK_ANSI_7, 0, IDC_SELECT_TAB_6}, 49 {true, false, false, false, kVK_ANSI_Keypad7, 0, IDC_SELECT_TAB_6}, 50 {true, false, false, false, kVK_ANSI_8, 0, IDC_SELECT_TAB_7}, 51 {true, false, false, false, kVK_ANSI_Keypad8, 0, IDC_SELECT_TAB_7}, 52 {true, false, false, false, kVK_ANSI_9, 0, IDC_SELECT_LAST_TAB}, 53 {true, false, false, false, kVK_ANSI_Keypad9, 0, IDC_SELECT_LAST_TAB}, 54 }; 55 56 *num_entries = arraysize(keyboard_shortcuts); 57 58 return keyboard_shortcuts; 59 } 60 61 const KeyboardShortcutData* GetDelayedWindowKeyboardShortcutTable( 62 size_t* num_entries) { 63 static const KeyboardShortcutData keyboard_shortcuts[] = { 64 //cmd shift cntrl option 65 //--- ----- ----- ------ 66 {false, false, false, false, kVK_Escape, 0, IDC_STOP}, 67 }; 68 69 *num_entries = arraysize(keyboard_shortcuts); 70 71 return keyboard_shortcuts; 72 } 73 74 const KeyboardShortcutData* GetBrowserKeyboardShortcutTable( 75 size_t* num_entries) { 76 static const KeyboardShortcutData keyboard_shortcuts[] = { 77 //cmd shift cntrl option 78 //--- ----- ----- ------ 79 {true, false, false, false, kVK_LeftArrow, 0, IDC_BACK}, 80 {true, false, false, false, kVK_RightArrow, 0, IDC_FORWARD}, 81 {false, false, false, false, kVK_Delete, 0, IDC_BACK}, 82 {false, true, false, false, kVK_Delete, 0, IDC_FORWARD}, 83 {true, true, false, false, 0, 'c', IDC_DEV_TOOLS_INSPECT}, 84 }; 85 86 *num_entries = arraysize(keyboard_shortcuts); 87 88 return keyboard_shortcuts; 89 } 90 91 static bool MatchesEventForKeyboardShortcut( 92 const KeyboardShortcutData& shortcut, 93 bool command_key, bool shift_key, bool cntrl_key, bool opt_key, 94 int vkey_code, unichar key_char) { 95 // Expects that one of |key_char| or |vkey_code| is 0. 96 DCHECK((shortcut.key_char == 0) ^ (shortcut.vkey_code == 0)); 97 if (shortcut.key_char) { 98 // The given shortcut key is to be matched by a keyboard character. 99 // In this case we ignore shift and opt (alt) key modifiers, because 100 // the character may be generated by a combination with those keys. 101 if (shortcut.command_key == command_key && 102 shortcut.cntrl_key == cntrl_key && 103 shortcut.key_char == key_char) 104 return true; 105 } else if (shortcut.vkey_code) { 106 // The given shortcut key is to be matched by a virtual key code. 107 if (shortcut.command_key == command_key && 108 shortcut.shift_key == shift_key && 109 shortcut.cntrl_key == cntrl_key && 110 shortcut.opt_key == opt_key && 111 shortcut.vkey_code == vkey_code) 112 return true; 113 } else { 114 NOTREACHED(); // Shouldn't happen. 115 } 116 return false; 117 } 118 119 static int CommandForKeyboardShortcut( 120 const KeyboardShortcutData* (*get_keyboard_shortcut_table)(size_t*), 121 bool command_key, bool shift_key, bool cntrl_key, bool opt_key, 122 int vkey_code, unichar key_char) { 123 124 // Scan through keycodes and see if it corresponds to one of the global 125 // shortcuts on file. 126 // 127 // TODO(jeremy): Change this into a hash table once we get enough 128 // entries in the array to make a difference. 129 // (When turning this into a hash table, note that the current behavior 130 // relies on the order of the table (see the comment for '{' / '}' above). 131 size_t num_shortcuts = 0; 132 const KeyboardShortcutData *it = get_keyboard_shortcut_table(&num_shortcuts); 133 for (size_t i = 0; i < num_shortcuts; ++i, ++it) { 134 if (MatchesEventForKeyboardShortcut(*it, command_key, shift_key, cntrl_key, 135 opt_key, vkey_code, key_char)) 136 return it->chrome_command; 137 } 138 139 return -1; 140 } 141 142 int CommandForWindowKeyboardShortcut( 143 bool command_key, bool shift_key, bool cntrl_key, bool opt_key, 144 int vkey_code, unichar key_char) { 145 return CommandForKeyboardShortcut(GetWindowKeyboardShortcutTable, 146 command_key, shift_key, 147 cntrl_key, opt_key, vkey_code, 148 key_char); 149 } 150 151 int CommandForDelayedWindowKeyboardShortcut( 152 bool command_key, bool shift_key, bool cntrl_key, bool opt_key, 153 int vkey_code, unichar key_char) { 154 return CommandForKeyboardShortcut(GetDelayedWindowKeyboardShortcutTable, 155 command_key, shift_key, 156 cntrl_key, opt_key, vkey_code, 157 key_char); 158 } 159 160 int CommandForBrowserKeyboardShortcut( 161 bool command_key, bool shift_key, bool cntrl_key, bool opt_key, 162 int vkey_code, unichar key_char) { 163 return CommandForKeyboardShortcut(GetBrowserKeyboardShortcutTable, 164 command_key, shift_key, 165 cntrl_key, opt_key, vkey_code, 166 key_char); 167 } 168 169 unichar KeyCharacterForEvent(NSEvent* event) { 170 NSString* eventString = [event charactersIgnoringModifiers]; 171 NSString* characters = [event characters]; 172 173 // Character pairs that undergo BiDi mirrored. 174 // There are actually many more such pairs, but these are the ones that 175 // are likely to show up in keyboard shortcuts. 176 const struct { 177 unichar a; 178 unichar b; 179 } kMirroredBiDiChars[] = { 180 {'{', '}'}, 181 {'[', ']'}, 182 {'(', ')'}, 183 }; 184 185 if ([eventString length] != 1) 186 return 0; 187 188 if ([characters length] != 1) 189 return [eventString characterAtIndex:0]; 190 191 unichar noModifiersChar = [eventString characterAtIndex:0]; 192 unichar rawChar = [characters characterAtIndex:0]; 193 // When both |characters| and |charactersIgnoringModifiers| are ascii, 194 // return the first character of |characters|, if... 195 if (isascii(noModifiersChar) && isascii(rawChar)) { 196 // |characters| is an alphabet (mainly for dvorak-qwerty layout), or 197 if (isalpha(rawChar)) 198 return rawChar; 199 200 // http://crbug.com/42517 201 // In RTL keyboard layouts, Cocoa mirrors characters in the string 202 // returned by [event charactersIgnoringModifiers]. In this case, return 203 // the raw (unmirrored) char. 204 // FIXME: If there is a need to add any more characters to the 205 // kMirroredBiDiChars table, then it's probably better to use ICU's 206 // u_charMirror() function to perform this test. 207 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kMirroredBiDiChars); ++i) { 208 const unichar& a = kMirroredBiDiChars[i].a; 209 const unichar& b = kMirroredBiDiChars[i].b; 210 if ((rawChar == a && noModifiersChar == b) || 211 (rawChar == b && noModifiersChar == a)) 212 return rawChar; 213 } 214 215 // opt/alt modifier is set (e.g. on german layout we want '{' for opt-8). 216 if ([event modifierFlags] & NSAlternateKeyMask) 217 return [characters characterAtIndex:0]; 218 } 219 220 return [eventString characterAtIndex:0]; 221 } 222