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 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/accelerators/accelerator.h"
#include <stdint.h>
#include <tuple>
#include "base/check_op.h"
#include "base/i18n/rtl.h"
#include "base/notreached.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/cxx23_to_underlying.h"
#include "build/build_config.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/strings/grit/ui_strings.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#endif
#if !BUILDFLAG(IS_WIN) && (defined(USE_AURA) || BUILDFLAG(IS_MAC))
#include "ui/events/keycodes/keyboard_code_conversion.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "ui/base/accelerators/ash/quick_insert_event_property.h"
#include "ui/base/ui_base_features.h"
#endif
#if BUILDFLAG(USE_BLINK)
#include "ui/base/accelerators/media_keys_listener.h"
#endif
namespace ui {
Accelerator::Accelerator(const KeyEvent& key_event)
: key_code_(key_event.key_code()),
key_state_(key_event.type() == EventType::kKeyPressed
? KeyState::PRESSED
: KeyState::RELEASED),
// |modifiers_| may include the repeat flag.
modifiers_(key_event.flags() & kInterestingFlagsMask),
time_stamp_(key_event.time_stamp()),
interrupted_by_mouse_event_(false),
source_device_id_(key_event.source_device_id()) {
#if BUILDFLAG(IS_CHROMEOS)
if (features::IsImprovedKeyboardShortcutsEnabled()) {
code_ = key_event.code();
}
// Rewrite to Quick Insert based on the presence of the property.
if (key_event.key_code() == VKEY_ASSISTANT &&
HasQuickInsertProperty(key_event)) {
key_code_ = VKEY_QUICK_INSERT;
}
#endif
}
KeyEvent Accelerator::ToKeyEvent() const {
return KeyEvent(key_state() == Accelerator::KeyState::PRESSED
? EventType::kKeyPressed
: EventType::kKeyReleased,
key_code(),
#if BUILDFLAG(IS_CHROMEOS)
code(),
#endif
modifiers(), time_stamp());
}
#if BUILDFLAG(USE_BLINK)
bool Accelerator::IsMediaKey() const {
if (modifiers_ != EF_NONE) {
return false;
}
return ui::MediaKeysListener::IsMediaKeycode(key_code_);
}
#endif
std::vector<std::u16string> Accelerator::GetShortcutVectorRepresentation()
const {
std::vector<std::u16string> shortcut_vector;
if (IsEmpty()) {
return shortcut_vector;
}
std::u16string key_code = GetKeyCodeStringForShortcut();
#if BUILDFLAG(IS_MAC)
shortcut_vector = GetShortFormModifiers();
shortcut_vector.push_back(key_code);
#else
std::vector<std::u16string> modifiers = GetLongFormModifiers();
// For some reason, menus in Windows ignore standard Unicode directionality
// marks (such as LRE, PDF, etc.). On RTL locales, we use RTL menus and
// therefore any text we draw for the menu items is drawn in an RTL context.
// Thus, the text "Ctrl++" (which we currently use for the Zoom In option)
// appears as "++Ctrl" in RTL because the Unicode BiDi algorithm puts
// punctuation on the left when the context is right-to-left. Shortcuts that
// do not end with a punctuation mark (such as "Ctrl+H" do not have this
// problem).
//
// The only way to solve this problem is to adjust the shortcut representation
// if the locale is RTL so that it is drawn correctly in an RTL context.
// Instead of returning "Ctrl++" in the above example, we return "++Ctrl".
// This will cause the text to appear as "Ctrl++" when Windows draws the
// string in an RTL context because the punctuation no longer appears at the
// end of the string.
// To accomplish this, if the character used for the accelerator is not
// alphanumeric and the locale is RTL place the character key code before the
// modifiers. Otherwise, place it after the modifiers as per usual.
//
// TODO(crbug.com/40175605): This hack of doing the RTL adjustment here was
// intended to be removed when the menu system moved to MenuItemView. That was
// crbug.com/2822, closed in 2010. Can we finally remove all of this?
if (base::i18n::IsRTL() && key_code.length() == 1 &&
!base::IsAsciiAlphaNumeric(key_code[0])) {
shortcut_vector.push_back(key_code);
shortcut_vector.insert(shortcut_vector.end(), modifiers.begin(),
modifiers.end());
} else {
shortcut_vector.insert(shortcut_vector.end(), modifiers.begin(),
modifiers.end());
shortcut_vector.push_back(key_code);
}
#endif // BUILDFLAG(IS_MAC)
return shortcut_vector;
}
std::u16string Accelerator::GetShortcutText() const {
std::u16string shortcut;
std::vector<std::u16string> shortcut_vector =
GetShortcutVectorRepresentation();
// Shortcut text is expected to be represented using '+' as a separator on all
// platforms except Mac, where no separator is used.
#if BUILDFLAG(IS_MAC)
shortcut = base::JoinString(shortcut_vector, u"");
#else
shortcut = base::JoinString(shortcut_vector, u"+");
#endif
return shortcut;
}
std::u16string Accelerator::GetKeyCodeStringForShortcut() const {
std::u16string key_string;
#if BUILDFLAG(IS_MAC)
key_string = KeyCodeToMacSymbol();
#else
key_string = KeyCodeToName();
#endif
if (key_string.empty()) {
#if BUILDFLAG(IS_WIN)
// Our fallback is to try translate the key code to a regular character
// unless it is one of digits (VK_0 to VK_9). Some keyboard
// layouts have characters other than digits assigned in
// an unshifted mode (e.g. French AZERY layout has 'a with grave
// accent' for '0'). For display in the menu (e.g. Ctrl-0 for the
// default zoom level), we leave VK_[0-9] alone without translation.
wchar_t key;
if (base::IsAsciiDigit(base::to_underlying(key_code_))) {
key = static_cast<wchar_t>(key_code_);
} else {
key = LOWORD(::MapVirtualKeyW(key_code_, MAPVK_VK_TO_CHAR));
}
// If there is no translation for the given |key_code_| (e.g.
// VKEY_UNKNOWN), |::MapVirtualKeyW| returns 0.
if (key != 0) {
key_string += key;
}
#elif defined(USE_AURA) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID)
const uint16_t c = DomCodeToUsLayoutCharacter(
UsLayoutKeyboardCodeToDomCode(key_code_), false);
if (c != 0) {
key_string +=
static_cast<std::u16string::value_type>(base::ToUpperASCII(c));
}
#endif
}
return key_string;
}
#if BUILDFLAG(IS_MAC)
// In macOS 10.13, the glyphs used for page up, page down, home, and end were
// changed from the arrows below to new, skinny arrows. The tricky bit is that
// the underlying Unicode characters weren't changed, just the font used. Maybe
// the keyboard font, CTFontCreateUIFontForLanguage, with key
// kCTFontUIFontMenuItemCmdKey, can be used everywhere this symbol is used. (If
// so, then the RTL stuff will need to be removed.)
std::u16string Accelerator::KeyCodeToMacSymbol() const {
switch (key_code_) {
case VKEY_CAPITAL:
return u"⇪"; // U+21EA, UPWARDS WHITE ARROW FROM BAR
case VKEY_RETURN:
return u"⌤"; // U+2324, UP ARROWHEAD BETWEEN TWO HORIZONTAL BARS
case VKEY_BACK:
return u"⌫"; // U+232B, ERASE TO THE LEFT
case VKEY_ESCAPE:
return u"⎋"; // U+238B, BROKEN CIRCLE WITH NORTHWEST ARROW
case VKEY_RIGHT:
return u"→"; // U+2192, RIGHTWARDS ARROW
case VKEY_LEFT:
return u"←"; // U+2190, LEFTWARDS ARROW
case VKEY_UP:
return u"↑"; // U+2191, UPWARDS ARROW
case VKEY_DOWN:
return u"↓"; // U+2193, DOWNWARDS ARROW
case VKEY_PRIOR:
return u"⇞"; // U+21DE, UPWARDS ARROW WITH DOUBLE STROKE
case VKEY_NEXT:
return u"⇟"; // U+21DF, DOWNWARDS ARROW WITH DOUBLE STROKE
case VKEY_HOME:
return base::i18n::IsRTL() ? u"↗" // U+2197, NORTH EAST ARROW
: u"↖"; // U+2196, NORTH WEST ARROW
case VKEY_END:
return base::i18n::IsRTL() ? u"↙" // U+2199, SOUTH WEST ARROW
: u"↘"; // U+2198, SOUTH EAST ARROW
case VKEY_TAB:
return u"⇥"; // U+21E5, RIGHTWARDS ARROW TO BAR
// Mac has a shift-tab icon ("⇤", U+21E4, LEFTWARDS ARROW TO BAR) but we
// don't use it. "Space" and some other keys are written out; fall back to
// KeyCodeToName() for those (and any other unhandled keys).
default:
return KeyCodeToName();
}
}
#endif // BUILDFLAG(IS_MAC)
std::u16string Accelerator::KeyCodeToName() const {
int string_id = 0;
switch (key_code_) {
case VKEY_TAB:
string_id = IDS_APP_TAB_KEY;
break;
case VKEY_RETURN:
string_id = IDS_APP_ENTER_KEY;
break;
case VKEY_SPACE:
string_id = IDS_APP_SPACE_KEY;
break;
case VKEY_PRIOR:
string_id = IDS_APP_PAGEUP_KEY;
break;
case VKEY_NEXT:
string_id = IDS_APP_PAGEDOWN_KEY;
break;
case VKEY_END:
string_id = IDS_APP_END_KEY;
break;
case VKEY_HOME:
string_id = IDS_APP_HOME_KEY;
break;
case VKEY_INSERT:
string_id = IDS_APP_INSERT_KEY;
break;
case VKEY_DELETE:
string_id = IDS_APP_DELETE_KEY;
break;
case VKEY_LEFT:
string_id = IDS_APP_LEFT_ARROW_KEY;
break;
case VKEY_RIGHT:
string_id = IDS_APP_RIGHT_ARROW_KEY;
break;
case VKEY_UP:
string_id = IDS_APP_UP_ARROW_KEY;
break;
case VKEY_DOWN:
string_id = IDS_APP_DOWN_ARROW_KEY;
break;
case VKEY_ESCAPE:
string_id = IDS_APP_ESC_KEY;
break;
case VKEY_BACK:
string_id = IDS_APP_BACKSPACE_KEY;
break;
case VKEY_F1:
string_id = IDS_APP_F1_KEY;
break;
case VKEY_F6:
string_id = IDS_APP_F6_KEY;
break;
case VKEY_F11:
string_id = IDS_APP_F11_KEY;
break;
#if !BUILDFLAG(IS_MAC)
// On Mac, commas and periods are used literally in accelerator text.
case VKEY_OEM_COMMA:
string_id = IDS_APP_COMMA_KEY;
break;
case VKEY_OEM_PERIOD:
string_id = IDS_APP_PERIOD_KEY;
break;
#endif
case VKEY_MEDIA_NEXT_TRACK:
string_id = IDS_APP_MEDIA_NEXT_TRACK_KEY;
break;
case VKEY_MEDIA_PLAY_PAUSE:
string_id = IDS_APP_MEDIA_PLAY_PAUSE_KEY;
break;
case VKEY_MEDIA_PREV_TRACK:
string_id = IDS_APP_MEDIA_PREV_TRACK_KEY;
break;
case VKEY_MEDIA_STOP:
string_id = IDS_APP_MEDIA_STOP_KEY;
break;
default:
break;
}
return string_id ? l10n_util::GetStringUTF16(string_id) : std::u16string();
}
std::vector<std::u16string> Accelerator::GetLongFormModifiers() const {
std::vector<std::u16string> modifiers;
if (IsCmdDown()) {
#if BUILDFLAG(IS_MAC)
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_COMMAND_KEY));
#elif BUILDFLAG(IS_CHROMEOS)
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_SEARCH_KEY));
#elif BUILDFLAG(IS_WIN)
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_WINDOWS_KEY));
#elif BUILDFLAG(IS_LINUX)
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_SUPER_KEY));
#else
NOTREACHED();
#endif
}
if (IsAltDown()) {
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_ALT_KEY));
}
if (IsCtrlDown()) {
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_CTRL_KEY));
}
if (IsShiftDown()) {
modifiers.push_back(l10n_util::GetStringUTF16(IDS_APP_SHIFT_KEY));
}
return modifiers;
}
std::vector<std::u16string> Accelerator::GetShortFormModifiers() const {
std::vector<std::u16string> modifiers;
// Add modifiers in the order that matches how they are displayed in native
// menus.
if (IsCtrlDown()) {
modifiers.push_back(u"⌃"); // U+2303, UP ARROWHEAD
}
if (IsAltDown()) {
modifiers.push_back(u"⌥"); // U+2325, OPTION KEY
}
if (IsShiftDown()) {
modifiers.push_back(u"⇧"); // U+21E7, UPWARDS WHITE ARROW
}
if (IsCmdDown()) {
modifiers.push_back(u"⌘"); // U+2318, PLACE OF INTEREST SIGN
}
if (IsFunctionDown()) {
// The real "fn" used by menus is actually U+E23E in the Private Use Area in
// the keyboard font obtained with CTFontCreateUIFontForLanguage, with key
// kCTFontUIFontMenuItemCmdKey. Because this function must return a raw
// Unicode string with no specified font, return a string of characters.
//
// Newer Mac keyboards have a globe symbol on the fn key that is used in
// menus instead of "fn". That globe symbol is actually U+1F310 + U+FE0E,
// the emoji globe + the variation selector that indicates the text-style
// presentation. (🌐︎)
//
// Whether or not "fn" or the globe is displayed as the menu shortcut
// modifier depends on whether there is an attached keyboard with a globe
// symbol on it. Rather than rummaging around in the IORegistry, where the
// HID driver for the keyboard has a SupportsGlobeKey = True property, it's
// probably best to just make a call to the HIServices function
// HIS_XPC_GetGlobeKeyAvailability() and let it do the magic. See AppKit's
// -[NSKeyboardShortcut localizedModifierMaskDisplayName] for an example of
// this.
//
// TODO(http://crbug.com/40800376): Implement all of this when text-style
// presentations are implemented for Views in https://crbug.com/40137571.
modifiers.push_back(u"(fn) ");
}
return modifiers;
}
} // namespace ui
|