// 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));
}
}
}
}
}