global_keyboard_shortcuts_mac.mm 8.16 KB
Newer Older
1
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/global_keyboard_shortcuts_mac.h"

7 8
#import <AppKit/AppKit.h>

9
#include "base/logging.h"
10
#include "base/macros.h"
11
#include "chrome/app/chrome_command_ids.h"
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
#import "chrome/browser/ui/cocoa/nsmenuitem_additions.h"

namespace {

// Returns the menu item associated with |key| in |menu|, or nil if not found.
NSMenuItem* FindMenuItem(NSEvent* key, NSMenu* menu) {
  NSMenuItem* result = nil;

  for (NSMenuItem* item in [menu itemArray]) {
    NSMenu* submenu = [item submenu];
    if (submenu) {
      if (submenu != [NSApp servicesMenu])
        result = FindMenuItem(key, submenu);
    } else if ([item cr_firesForKeyEventIfEnabled:key]) {
      result = item;
    }

    if (result)
      break;
  }

  return result;
}

36 37 38 39 40 41 42
bool MatchesEventForKeyboardShortcut(const KeyboardShortcutData& shortcut,
                                     bool command_key,
                                     bool shift_key,
                                     bool cntrl_key,
                                     bool opt_key,
                                     int vkey_code,
                                     unichar key_char) {
43 44 45
  // Expects that one of |key_char| or |vkey_code| is 0.
  DCHECK((shortcut.key_char == 0) ^ (shortcut.vkey_code == 0));
  if (shortcut.key_char) {
46 47 48
    // Shortcuts that have a |key_char| and have |opt_key| set are mistakes,
    // since |opt_key| is not checked when there is a |key_char|.
    DCHECK(!shortcut.opt_key);
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    // 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;
}

70 71 72 73 74 75 76
int CommandForKeyboardShortcut(const std::vector<KeyboardShortcutData>& table,
                               bool command_key,
                               bool shift_key,
                               bool cntrl_key,
                               bool opt_key,
                               int vkey_code,
                               unichar key_char) {
77 78 79 80 81
  // 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.
82 83
  // (When turning this into a hash table, note that the current behavior
  // relies on the order of the table (see the comment for '{' / '}' above).
84 85 86 87 88
  for (const auto& shortcut : table) {
    if (MatchesEventForKeyboardShortcut(shortcut, command_key, shift_key,
                                        cntrl_key, opt_key, vkey_code,
                                        key_char))
      return shortcut.chrome_command;
89 90 91 92
  }

  return -1;
}
93

94 95
}  // namespace

96
int CommandForWindowKeyboardShortcut(
thakis@chromium.org's avatar
thakis@chromium.org committed
97
    bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
98
    int vkey_code, unichar key_char) {
99
  return CommandForKeyboardShortcut(GetWindowKeyboardShortcutTable(),
100
                                    command_key, shift_key,
101 102
                                    cntrl_key, opt_key, vkey_code,
                                    key_char);
103 104
}

105 106
int CommandForDelayedWindowKeyboardShortcut(
    bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
107
    int vkey_code, unichar key_char) {
108
  return CommandForKeyboardShortcut(GetDelayedWindowKeyboardShortcutTable(),
109
                                    command_key, shift_key,
110 111
                                    cntrl_key, opt_key, vkey_code,
                                    key_char);
112 113
}

114
int CommandForBrowserKeyboardShortcut(
thakis@chromium.org's avatar
thakis@chromium.org committed
115
    bool command_key, bool shift_key, bool cntrl_key, bool opt_key,
116
    int vkey_code, unichar key_char) {
117
  return CommandForKeyboardShortcut(GetBrowserKeyboardShortcutTable(),
118
                                    command_key, shift_key,
119 120 121 122
                                    cntrl_key, opt_key, vkey_code,
                                    key_char);
}

123 124 125 126
int CommandForKeyEvent(NSEvent* event) {
  if ([event type] != NSKeyDown)
    return -1;

127 128 129
  int cmdNum = MenuCommandForKeyEvent(event);
  if (cmdNum != -1)
    return cmdNum;
130 131 132 133 134 135 136 137 138 139

  // Look in secondary keyboard shortcuts.
  NSUInteger modifiers = [event modifierFlags];
  const bool cmdKey = (modifiers & NSCommandKeyMask) != 0;
  const bool shiftKey = (modifiers & NSShiftKeyMask) != 0;
  const bool cntrlKey = (modifiers & NSControlKeyMask) != 0;
  const bool optKey = (modifiers & NSAlternateKeyMask) != 0;
  const int keyCode = [event keyCode];
  const unichar keyChar = KeyCharacterForEvent(event);

140 141
  cmdNum = CommandForWindowKeyboardShortcut(cmdKey, shiftKey, cntrlKey, optKey,
                                            keyCode, keyChar);
142 143 144 145 146 147 148 149 150 151 152
  if (cmdNum != -1)
    return cmdNum;

  cmdNum = CommandForBrowserKeyboardShortcut(
      cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
  if (cmdNum != -1)
    return cmdNum;

  return -1;
}

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
int MenuCommandForKeyEvent(NSEvent* event) {
  if ([event type] != NSKeyDown)
    return -1;

  // Look in menu.
  NSMenuItem* item = FindMenuItem(event, [NSApp mainMenu]);

  if (!item)
    return -1;

  if ([item action] == @selector(commandDispatch:) && [item tag] > 0)
    return [item tag];

  // "Close window" doesn't use the |commandDispatch:| mechanism. Menu items
  // that do not correspond to IDC_ constants need no special treatment however,
  // as they can't be blacklisted in
  // |BrowserCommandController::IsReservedCommandOrKey()| anyhow.
  if (item && [item action] == @selector(performClose:))
    return IDC_CLOSE_WINDOW;

  // "Exit" doesn't use the |commandDispatch:| mechanism either.
  if ([item action] == @selector(terminate:))
    return IDC_EXIT;

  return -1;
}

180
unichar KeyCharacterForEvent(NSEvent* event) {
181 182 183
  NSString* eventString = [event charactersIgnoringModifiers];
  NSString* characters = [event characters];

184 185 186 187 188 189
  if ([eventString length] != 1)
    return 0;

  if ([characters length] != 1)
    return [eventString characterAtIndex:0];

190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
  // 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.
    {'[', ']', '['},
    {']', '[', ']'},
  };

215 216
  unichar noModifiersChar = [eventString characterAtIndex:0];
  unichar rawChar = [characters characterAtIndex:0];
217 218

  // Only apply transformation table for ascii.
219
  if (isascii(noModifiersChar) && isascii(rawChar)) {
220 221
    // Alphabetic characters aren't mirrored, go with the raw character.
    // [A previous partial comment said something about Dvorak?]
222 223 224 225
    if (isalpha(rawChar))
      return rawChar;

    // http://crbug.com/42517
226
    // http://crbug.com/315379
227 228 229
    // In RTL keyboard layouts, Cocoa mirrors characters in the string
    // returned by [event charactersIgnoringModifiers].  In this case, return
    // the raw (unmirrored) char.
230
    for (size_t i = 0; i < arraysize(kCharMapping); ++i) {
231 232 233 234
      if (rawChar == kCharMapping[i].rawChar &&
          noModifiersChar == kCharMapping[i].unmodChar) {
        return kCharMapping[i].targetChar;
      }
235 236
    }

237 238
    // opt/alt modifier is set (e.g. on german layout we want '{' for opt-8).
    if ([event modifierFlags] & NSAlternateKeyMask)
239
      return rawChar;
240 241
  }

242
  return noModifiersChar;
243
}