| // 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 |