| // Copyright 2009 The Chromium Authors |
| // 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" |
| |
| #include <AppKit/NSEvent.h> |
| #include <Carbon/Carbon.h> |
| #include <stddef.h> |
| |
| #include <initializer_list> |
| |
| #include "base/check_op.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/base/buildflags.h" |
| #include "ui/events/keycodes/keyboard_code_conversion_mac.h" |
| |
| namespace { |
| |
| enum class CommandKeyState : bool { |
| kUp, |
| kDown, |
| }; |
| enum class ShiftKeyState : bool { |
| kUp, |
| kDown, |
| }; |
| enum class OptionKeyState : bool { |
| kUp, |
| kDown, |
| }; |
| enum class ControlKeyState : bool { |
| kUp, |
| kDown, |
| }; |
| |
| int CommandForKeys(int vkey_code, |
| CommandKeyState command, |
| ShiftKeyState shift = ShiftKeyState::kUp, |
| OptionKeyState option = OptionKeyState::kUp, |
| ControlKeyState control = ControlKeyState::kUp) { |
| NSUInteger modifier_flags = 0; |
| if (command == CommandKeyState::kDown) |
| modifier_flags |= NSEventModifierFlagCommand; |
| if (shift == ShiftKeyState::kDown) |
| modifier_flags |= NSEventModifierFlagShift; |
| if (option == OptionKeyState::kDown) |
| modifier_flags |= NSEventModifierFlagOption; |
| if (control == ControlKeyState::kDown) |
| modifier_flags |= NSEventModifierFlagControl; |
| |
| switch (vkey_code) { |
| case kVK_UpArrow: |
| case kVK_DownArrow: |
| case kVK_LeftArrow: |
| case kVK_RightArrow: |
| // Docs say that this is set for numpad *and* arrow keys. |
| modifier_flags |= NSEventModifierFlagNumericPad; |
| [[fallthrough]]; |
| case kVK_Help: |
| case kVK_ForwardDelete: |
| case kVK_Home: |
| case kVK_End: |
| case kVK_PageUp: |
| case kVK_PageDown: |
| // Docs say that this is set for function keys *and* the cluster of six |
| // navigation keys in the center of the keyboard *and* arrow keys. |
| modifier_flags |= NSEventModifierFlagFunction; |
| break; |
| default: |
| break; |
| } |
| |
| unichar shifted_character; |
| unichar character; |
| int result = ui::MacKeyCodeForWindowsKeyCode( |
| ui::KeyboardCodeFromKeyCode(vkey_code), modifier_flags, |
| &shifted_character, &character); |
| DCHECK_NE(result, -1); |
| |
| NSEvent* event = [NSEvent |
| keyEventWithType:NSEventTypeKeyDown |
| location:NSZeroPoint |
| modifierFlags:modifier_flags |
| timestamp:0.0 |
| windowNumber:0 |
| context:nil |
| characters:[NSString stringWithFormat:@"%C", character] |
| charactersIgnoringModifiers:[NSString |
| stringWithFormat:@"%C", shifted_character] |
| isARepeat:NO |
| keyCode:vkey_code]; |
| |
| return CommandForKeyEvent(event).chrome_command; |
| } |
| |
| } // namespace |
| |
| TEST(GlobalKeyboardShortcuts, BasicFunctionality) { |
| // Test that an invalid shortcut translates into an invalid command id. |
| const int kInvalidCommandId = -1; |
| const int no_key_code = 0; |
| EXPECT_EQ( |
| kInvalidCommandId, |
| CommandForKeys(no_key_code, CommandKeyState::kUp, ShiftKeyState::kUp, |
| OptionKeyState::kUp, ControlKeyState::kUp)); |
| |
| // Check that all known keyboard shortcuts return valid results. |
| for (const auto& shortcut : GetShortcutsNotPresentInMainMenu()) { |
| CommandKeyState command = |
| shortcut.command_key ? CommandKeyState::kDown : CommandKeyState::kUp; |
| ShiftKeyState shift = |
| shortcut.shift_key ? ShiftKeyState::kDown : ShiftKeyState::kUp; |
| OptionKeyState option = |
| shortcut.opt_key ? OptionKeyState::kDown : OptionKeyState::kUp; |
| ControlKeyState control = |
| shortcut.cntrl_key ? ControlKeyState::kDown : ControlKeyState::kUp; |
| |
| int cmd_num = |
| CommandForKeys(shortcut.vkey_code, command, shift, option, control); |
| EXPECT_EQ(cmd_num, shortcut.chrome_command); |
| } |
| // Test that switching tabs triggers off keycodes and not characters (visible |
| // with the Italian keyboard layout). |
| EXPECT_EQ( |
| IDC_SELECT_TAB_0, |
| CommandForKeys(kVK_ANSI_1, CommandKeyState::kDown, ShiftKeyState::kUp, |
| OptionKeyState::kUp, ControlKeyState::kUp)); |
| } |
| |
| TEST(GlobalKeyboardShortcuts, KeypadNumberKeysMatch) { |
| // Test that the shortcuts that are generated by keypad number keys match the |
| // equivalent keys. |
| static const struct { |
| int keycode; |
| int keypad_keycode; |
| } equivalents[] = { |
| {kVK_ANSI_0, kVK_ANSI_Keypad0}, |
| {kVK_ANSI_1, kVK_ANSI_Keypad1}, |
| {kVK_ANSI_2, kVK_ANSI_Keypad2}, |
| {kVK_ANSI_3, kVK_ANSI_Keypad3}, |
| {kVK_ANSI_4, kVK_ANSI_Keypad4}, |
| {kVK_ANSI_5, kVK_ANSI_Keypad5}, |
| {kVK_ANSI_6, kVK_ANSI_Keypad6}, |
| {kVK_ANSI_7, kVK_ANSI_Keypad7}, |
| {kVK_ANSI_8, kVK_ANSI_Keypad8}, |
| {kVK_ANSI_9, kVK_ANSI_Keypad9}, |
| }; |
| |
| // We only consider unshifted keys. A shifted numpad key gives a different |
| // keyEquivalent than a shifted number key. |
| const ShiftKeyState shift = ShiftKeyState::kUp; |
| for (auto equivalent : equivalents) { |
| for (CommandKeyState command : |
| {CommandKeyState::kUp, CommandKeyState::kDown}) { |
| for (OptionKeyState option : |
| {OptionKeyState::kUp, OptionKeyState::kDown}) { |
| for (ControlKeyState control : |
| {ControlKeyState::kUp, ControlKeyState::kDown}) { |
| EXPECT_EQ(CommandForKeys(equivalent.keycode, command, shift, option, |
| control), |
| CommandForKeys(equivalent.keypad_keycode, command, shift, |
| option, control)); |
| } |
| } |
| } |
| } |
| } |