1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 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 180 181 182 183 184 185 186 187 188 189 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 215 216 217 218 219 220 221 222 223 224 225 226 227 228
|
// 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 "chrome/browser/global_keyboard_shortcuts_mac.h"
#import <AppKit/AppKit.h>
#include "base/logging.h"
#include "base/macros.h"
#include "chrome/app/chrome_command_ids.h"
#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;
}
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) {
// 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);
// 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;
}
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) {
// 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).
for (const auto& shortcut : table) {
if (MatchesEventForKeyboardShortcut(shortcut, command_key, shift_key,
cntrl_key, opt_key, vkey_code,
key_char))
return shortcut.chrome_command;
}
return -1;
}
} // namespace
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);
}
int CommandForKeyEvent(NSEvent* event) {
if ([event type] != NSKeyDown)
return -1;
// Look in menu.
NSMenuItem* item = FindMenuItem(event, [NSApp mainMenu]);
if (item && [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 && [item action] == @selector(terminate:))
return IDC_EXIT;
// 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);
int cmdNum = CommandForWindowKeyboardShortcut(
cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
if (cmdNum != -1)
return cmdNum;
cmdNum = CommandForBrowserKeyboardShortcut(
cmdKey, shiftKey, cntrlKey, optKey, keyCode, keyChar);
if (cmdNum != -1)
return cmdNum;
return -1;
}
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;
}
|