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
|
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "content/browser/cocoa/system_hotkey_map.h"
#import <Carbon/Carbon.h>
#include "base/apple/foundation_util.h"
#pragma mark - SystemHotkey
namespace content {
struct SystemHotkey {
unsigned short key_code;
NSUInteger modifiers;
};
#pragma mark - SystemHotkeyMap
SystemHotkeyMap::SystemHotkeyMap() = default;
SystemHotkeyMap::SystemHotkeyMap(SystemHotkeyMap&&) = default;
SystemHotkeyMap::~SystemHotkeyMap() = default;
bool SystemHotkeyMap::ParseDictionary(NSDictionary* dictionary) {
system_hotkeys_.clear();
if (!dictionary) {
return false;
}
NSDictionary* user_hotkey_dictionaries =
base::apple::ObjCCast<NSDictionary>(dictionary[@"AppleSymbolicHotKeys"]);
if (!user_hotkey_dictionaries) {
return false;
}
// Start with a dictionary of default macOS hotkeys that are not necessarily
// listed in com.apple.symbolichotkeys.plist, but should still be handled as
// reserved.
// If the user has overridden or disabled any of these hotkeys,
// [NSMutableDictionary addEntriesFromDictionary:] will ensure that the new
// values are used.
// See https://crbug.com/145062#c8
NSMutableDictionary* hotkey_dictionaries = [@{
// Default Window switch key binding: Command + `
// Note: The first parameter @96 is not used by |SystemHotkeyMap|.
@"27" : @{
@"enabled" : @YES,
@"value" : @{
@"type" : @"standard",
@"parameters" : @[
@96 /* unused */, @(kVK_ANSI_Grave), @(NSEventModifierFlagCommand)
],
}
}
} mutableCopy];
[hotkey_dictionaries addEntriesFromDictionary:user_hotkey_dictionaries];
// The meanings of the keys in `user_hotkey_dictionaries` are listed here:
// https://web.archive.org/web/20141112224103/http://hintsforums.macworld.com/showthread.php?t=114785
// Of particular interest are the following:
//
// # Spaces Left - Control, Left
// 79 = { enabled = 1; ... };
//
// # Spaces Right - Control, Right
// 81 = { enabled = 1; ... };
//
// Apparently, when you change the shortcuts for Spaces Left/Right, macOS
// also inserts entries at slots 80 and 82 which differ from the previous
// entries by the addition of the Shift key. This is similar to entries 60
// and 61 as documented in the web page above where Command, Option, Space
// cycles to the previous input source and Command, Option, Space, Shift
// cycles to the next. This approach doesn't make sense for moving between
// Spaces using the arrow keys. Maybe there's legacy machinery in the AppKit
// that automatically creates the shifted versions and macOS knows to ignore
// them (not expecting any non-system applications to read this file).
//
// Treating these shortcuts as valid results in unexpected behavior. For
// example, in the case of "Spaces Left" being mapped to Option Left Arrow,
// Chrome would silently ignore Shift-Option Left Arrow, the shortcut which
// appends the current text selection by one word to the left. To avoid
// this, we'll ignore these two undocumented shortcuts.
// See https://crbug.com/874219 .
const NSString* kSpacesLeftShiftedHotkeyId = @"80";
const NSString* kSpacesRightShiftedHotkeyId = @"82";
[hotkey_dictionaries removeObjectForKey:kSpacesLeftShiftedHotkeyId];
[hotkey_dictionaries removeObjectForKey:kSpacesRightShiftedHotkeyId];
for (NSString* system_hotkey_identifier in [hotkey_dictionaries allKeys]) {
if (![system_hotkey_identifier isKindOfClass:[NSString class]]) {
continue;
}
NSDictionary* hotkey_dictionary = base::apple::ObjCCast<NSDictionary>(
hotkey_dictionaries[system_hotkey_identifier]);
if (!hotkey_dictionary) {
continue;
}
NSNumber* enabled =
base::apple::ObjCCast<NSNumber>(hotkey_dictionary[@"enabled"]);
if (!enabled.boolValue) {
continue;
}
NSDictionary* value =
base::apple::ObjCCast<NSDictionary>(hotkey_dictionary[@"value"]);
if (!value) {
continue;
}
NSString* type = base::apple::ObjCCast<NSString>(value[@"type"]);
if (![type isEqualToString:@"standard"]) {
continue;
}
NSArray* parameters = base::apple::ObjCCast<NSArray>(value[@"parameters"]);
if (parameters.count != 3) {
continue;
}
const int kKeyCodeIndex = 1;
NSNumber* key_code =
base::apple::ObjCCast<NSNumber>(parameters[kKeyCodeIndex]);
if (!key_code) {
continue;
}
const int kModifierIndex = 2;
NSNumber* modifiers =
base::apple::ObjCCast<NSNumber>(parameters[kModifierIndex]);
if (!modifiers) {
continue;
}
ReserveHotkey(key_code.unsignedShortValue, modifiers.unsignedIntegerValue,
system_hotkey_identifier);
}
return true;
}
bool SystemHotkeyMap::IsEventReserved(NSEvent* event) const {
return IsHotkeyReserved(event.keyCode, event.modifierFlags);
}
bool SystemHotkeyMap::IsHotkeyReserved(unsigned short key_code,
NSUInteger modifiers) const {
modifiers &= NSEventModifierFlagDeviceIndependentFlagsMask;
std::vector<SystemHotkey>::const_iterator it;
for (it = system_hotkeys_.begin(); it != system_hotkeys_.end(); ++it) {
if (it->key_code == key_code && it->modifiers == modifiers) {
return true;
}
}
return false;
}
void SystemHotkeyMap::ReserveHotkey(unsigned short key_code,
NSUInteger modifiers,
NSString* system_hotkey_identifier) {
ReserveHotkey(key_code, modifiers);
// If a hotkey exists for cycling through the windows of an application, then
// adding shift to that hotkey cycles through the windows backwards.
NSString* kCycleThroughWindowsHotkeyId = @"27";
const NSUInteger kCycleBackwardsModifier =
modifiers | NSEventModifierFlagShift;
if ([system_hotkey_identifier isEqualToString:kCycleThroughWindowsHotkeyId] &&
modifiers != kCycleBackwardsModifier) {
ReserveHotkey(key_code, kCycleBackwardsModifier);
}
}
void SystemHotkeyMap::ReserveHotkey(unsigned short key_code,
NSUInteger modifiers) {
// Hotkeys require at least one of control, command, or alternate keys to be
// down.
NSUInteger required_modifiers = NSEventModifierFlagControl |
NSEventModifierFlagCommand |
NSEventModifierFlagOption;
if ((modifiers & required_modifiers) == 0) {
return;
}
SystemHotkey hotkey;
hotkey.key_code = key_code;
hotkey.modifiers = modifiers;
system_hotkeys_.push_back(hotkey);
}
} // namespace content
|