| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <AppKit/NSEvent.h> |
| #include <Carbon/Carbon.h> |
| |
| #include "chrome/browser/global_keyboard_shortcuts_mac.h" |
| |
| #include "base/basictypes.h" |
| #include "base/logging.h" |
| #include "chrome/app/chrome_command_ids.h" |
| |
| // Basically, there are two kinds of keyboard shortcuts: Ones that should work |
| // only if the tab contents is focused (BrowserKeyboardShortcut), and ones that |
| // should work in all other cases (WindowKeyboardShortcut). In the latter case, |
| // we differentiate between shortcuts that are checked before any other view |
| // gets the chance to handle them (WindowKeyboardShortcut) or after all views |
| // had a chance but did not handle the keypress event |
| // (DelayedWindowKeyboardShortcut). |
| |
| const KeyboardShortcutData* GetWindowKeyboardShortcutTable( |
| size_t* num_entries) { |
| static const KeyboardShortcutData keyboard_shortcuts[] = { |
| //cmd shift cntrl option |
| //--- ----- ----- ------ |
| // '{' / '}' characters should be matched earlier than virtual key code |
| // (therefore we can match alt-8 as '{' on german keyboards). |
| {true, false, false, false, 0, '}', IDC_SELECT_NEXT_TAB}, |
| {true, false, false, false, 0, '{', IDC_SELECT_PREVIOUS_TAB}, |
| {false, false, true, false, kVK_PageDown, 0, IDC_SELECT_NEXT_TAB}, |
| {false, false, true, false, kVK_Tab, 0, IDC_SELECT_NEXT_TAB}, |
| {false, false, true, false, kVK_PageUp, 0, IDC_SELECT_PREVIOUS_TAB}, |
| {false, true, true, false, kVK_Tab, 0, IDC_SELECT_PREVIOUS_TAB}, |
| // Cmd-0..8 select the Nth tab, with cmd-9 being "last tab". |
| {true, false, false, false, kVK_ANSI_1, 0, IDC_SELECT_TAB_0}, |
| {true, false, false, false, kVK_ANSI_Keypad1, 0, IDC_SELECT_TAB_0}, |
| {true, false, false, false, kVK_ANSI_2, 0, IDC_SELECT_TAB_1}, |
| {true, false, false, false, kVK_ANSI_Keypad2, 0, IDC_SELECT_TAB_1}, |
| {true, false, false, false, kVK_ANSI_3, 0, IDC_SELECT_TAB_2}, |
| {true, false, false, false, kVK_ANSI_Keypad3, 0, IDC_SELECT_TAB_2}, |
| {true, false, false, false, kVK_ANSI_4, 0, IDC_SELECT_TAB_3}, |
| {true, false, false, false, kVK_ANSI_Keypad4, 0, IDC_SELECT_TAB_3}, |
| {true, false, false, false, kVK_ANSI_5, 0, IDC_SELECT_TAB_4}, |
| {true, false, false, false, kVK_ANSI_Keypad5, 0, IDC_SELECT_TAB_4}, |
| {true, false, false, false, kVK_ANSI_6, 0, IDC_SELECT_TAB_5}, |
| {true, false, false, false, kVK_ANSI_Keypad6, 0, IDC_SELECT_TAB_5}, |
| {true, false, false, false, kVK_ANSI_7, 0, IDC_SELECT_TAB_6}, |
| {true, false, false, false, kVK_ANSI_Keypad7, 0, IDC_SELECT_TAB_6}, |
| {true, false, false, false, kVK_ANSI_8, 0, IDC_SELECT_TAB_7}, |
| {true, false, false, false, kVK_ANSI_Keypad8, 0, IDC_SELECT_TAB_7}, |
| {true, false, false, false, kVK_ANSI_9, 0, IDC_SELECT_LAST_TAB}, |
| {true, false, false, false, kVK_ANSI_Keypad9, 0, IDC_SELECT_LAST_TAB}, |
| {true, true, false, false, kVK_ANSI_M, 0, IDC_SHOW_AVATAR_MENU}, |
| }; |
| |
| *num_entries = arraysize(keyboard_shortcuts); |
| |
| return keyboard_shortcuts; |
| } |
| |
| const KeyboardShortcutData* GetDelayedWindowKeyboardShortcutTable( |
| size_t* num_entries) { |
| static const KeyboardShortcutData keyboard_shortcuts[] = { |
| //cmd shift cntrl option |
| //--- ----- ----- ------ |
| {false, false, false, false, kVK_Escape, 0, IDC_STOP}, |
| }; |
| |
| *num_entries = arraysize(keyboard_shortcuts); |
| |
| return keyboard_shortcuts; |
| } |
| |
| const KeyboardShortcutData* GetBrowserKeyboardShortcutTable( |
| size_t* num_entries) { |
| static const KeyboardShortcutData keyboard_shortcuts[] = { |
| //cmd shift cntrl option |
| //--- ----- ----- ------ |
| {true, false, false, false, kVK_LeftArrow, 0, IDC_BACK}, |
| {true, false, false, false, kVK_RightArrow, 0, IDC_FORWARD}, |
| {false, false, false, false, kVK_Delete, 0, IDC_BACK}, |
| {false, true, false, false, kVK_Delete, 0, IDC_FORWARD}, |
| {true, true, false, false, 0, 'c', IDC_DEV_TOOLS_INSPECT}, |
| {true, true, false, false, kVK_ANSI_Period, 0, |
| IDC_TOGGLE_SPEECH_INPUT}, |
| }; |
| |
| *num_entries = arraysize(keyboard_shortcuts); |
| |
| return keyboard_shortcuts; |
| } |
| |
| static bool MatchesEventForKeyboardShortcut( |
| const KeyboardShortcutData& shortcut, |
| bool command_key, bool shift_key, bool cntrl_key, bool opt_key, |
| int vkey_code, unichar key_char) { |
| // Expects that one of |key_char| or |vkey_code| is 0. |
| DCHECK((shortcut.key_char == 0) ^ (shortcut.vkey_code == 0)); |
| if (shortcut.key_char) { |
| // The given shortcut key is to be matched by a keyboard character. |
| // In this case we ignore shift and opt (alt) key modifiers, because |
| // the character may be generated by a combination with those keys. |
| if (shortcut.command_key == command_key && |
| shortcut.cntrl_key == cntrl_key && |
| shortcut.key_char == key_char) |
| return true; |
| } else if (shortcut.vkey_code) { |
| // The given shortcut key is to be matched by a virtual key code. |
| if (shortcut.command_key == command_key && |
| shortcut.shift_key == shift_key && |
| shortcut.cntrl_key == cntrl_key && |
| shortcut.opt_key == opt_key && |
| shortcut.vkey_code == vkey_code) |
| return true; |
| } else { |
| NOTREACHED(); // Shouldn't happen. |
| } |
| return false; |
| } |
| |
| static int CommandForKeyboardShortcut( |
| const KeyboardShortcutData* (*get_keyboard_shortcut_table)(size_t*), |
| bool command_key, bool shift_key, bool cntrl_key, bool opt_key, |
| int vkey_code, unichar key_char) { |
| |
| // Scan through keycodes and see if it corresponds to one of the global |
| // shortcuts on file. |
| // |
| // TODO(jeremy): Change this into a hash table once we get enough |
| // entries in the array to make a difference. |
| // (When turning this into a hash table, note that the current behavior |
| // relies on the order of the table (see the comment for '{' / '}' above). |
| size_t num_shortcuts = 0; |
| const KeyboardShortcutData *it = get_keyboard_shortcut_table(&num_shortcuts); |
| for (size_t i = 0; i < num_shortcuts; ++i, ++it) { |
| if (MatchesEventForKeyboardShortcut(*it, command_key, shift_key, cntrl_key, |
| opt_key, vkey_code, key_char)) |
| return it->chrome_command; |
| } |
| |
| return -1; |
| } |
| |
| int CommandForWindowKeyboardShortcut( |
| bool command_key, bool shift_key, bool cntrl_key, bool opt_key, |
| int vkey_code, unichar key_char) { |
| return CommandForKeyboardShortcut(GetWindowKeyboardShortcutTable, |
| command_key, shift_key, |
| cntrl_key, opt_key, vkey_code, |
| key_char); |
| } |
| |
| int CommandForDelayedWindowKeyboardShortcut( |
| bool command_key, bool shift_key, bool cntrl_key, bool opt_key, |
| int vkey_code, unichar key_char) { |
| return CommandForKeyboardShortcut(GetDelayedWindowKeyboardShortcutTable, |
| command_key, shift_key, |
| cntrl_key, opt_key, vkey_code, |
| key_char); |
| } |
| |
| int CommandForBrowserKeyboardShortcut( |
| bool command_key, bool shift_key, bool cntrl_key, bool opt_key, |
| int vkey_code, unichar key_char) { |
| return CommandForKeyboardShortcut(GetBrowserKeyboardShortcutTable, |
| command_key, shift_key, |
| cntrl_key, opt_key, vkey_code, |
| key_char); |
| } |
| |
| unichar KeyCharacterForEvent(NSEvent* event) { |
| NSString* eventString = [event charactersIgnoringModifiers]; |
| NSString* characters = [event characters]; |
| |
| if ([eventString length] != 1) |
| return 0; |
| |
| if ([characters length] != 1) |
| return [eventString characterAtIndex:0]; |
| |
| // Some characters are BiDi mirrored. The mirroring is different |
| // for different OS versions. Instead of having a mirror table, map |
| // raw/processed pairs to desired outputs. |
| const struct { |
| unichar rawChar; |
| unichar unmodChar; |
| unichar targetChar; |
| } kCharMapping[] = { |
| // OSX 10.8 mirrors certain chars. |
| {'{', '}', '{'}, |
| {'}', '{', '}'}, |
| {'(', ')', '('}, |
| {')', '(', ')'}, |
| |
| // OSX 10.9 has the unshifted raw char. |
| {'[', '}', '{'}, |
| {']', '{', '}'}, |
| {'9', ')', '('}, |
| {'0', '(', ')'}, |
| |
| // These are the same either way. |
| {'[', ']', '['}, |
| {']', '[', ']'}, |
| }; |
| |
| unichar noModifiersChar = [eventString characterAtIndex:0]; |
| unichar rawChar = [characters characterAtIndex:0]; |
| |
| // Only apply transformation table for ascii. |
| if (isascii(noModifiersChar) && isascii(rawChar)) { |
| // Alphabetic characters aren't mirrored, go with the raw character. |
| // [A previous partial comment said something about Dvorak?] |
| if (isalpha(rawChar)) |
| return rawChar; |
| |
| // http://crbug.com/42517 |
| // http://crbug.com/315379 |
| // In RTL keyboard layouts, Cocoa mirrors characters in the string |
| // returned by [event charactersIgnoringModifiers]. In this case, return |
| // the raw (unmirrored) char. |
| for (size_t i = 0; i < arraysize(kCharMapping); ++i) { |
| if (rawChar == kCharMapping[i].rawChar && |
| noModifiersChar == kCharMapping[i].unmodChar) { |
| return kCharMapping[i].targetChar; |
| } |
| } |
| |
| // opt/alt modifier is set (e.g. on german layout we want '{' for opt-8). |
| if ([event modifierFlags] & NSAlternateKeyMask) |
| return rawChar; |
| } |
| |
| return noModifiersChar; |
| } |