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
content / browser / cocoa / system_hotkey_map.mm [blame]
// 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