// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/accelerators/accelerator_alias_converter.h"
#include <optional>
#include <vector>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/display/privacy_screen_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/input_device_settings/input_device_settings_controller_impl.h"
#include "ash/system/input_device_settings/input_device_settings_utils.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/fixed_flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/notreached.h"
#include "chromeos/constants/devicetype.h"
#include "components/prefs/pref_service.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/ash/keyboard_layout_util.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/ash/mojom/six_pack_shortcut_modifier.mojom-shared.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
namespace ash {
namespace {
using DeviceType = ui::KeyboardCapability::DeviceType;
bool IsChromeOSKeyboard(const ui::KeyboardDevice& keyboard) {
const auto device_type =
Shell::Get()->keyboard_capability()->GetDeviceType(keyboard);
return device_type == DeviceType::kDeviceInternalKeyboard ||
device_type == DeviceType::kDeviceExternalChromeOsKeyboard;
}
// Gets the most recently plugged in external keyboard. If there are no external
// keyboards, return the internal keyboard.
std::optional<ui::KeyboardDevice> GetPriorityExternalKeyboard() {
std::optional<ui::KeyboardDevice> priority_keyboard;
for (const ui::KeyboardDevice& keyboard :
ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
// If the input device settings controlled does not recognize the device as
// a keyboard, skip it.
if (features::IsInputDeviceSettingsSplitEnabled() &&
Shell::Get()->input_device_settings_controller()->GetKeyboardSettings(
keyboard.id) == nullptr) {
continue;
}
const auto device_type =
Shell::Get()->keyboard_capability()->GetDeviceType(keyboard);
switch (device_type) {
case DeviceType::kDeviceUnknown:
case DeviceType::kDeviceInternalKeyboard:
case DeviceType::kDeviceInternalRevenKeyboard:
break;
case DeviceType::kDeviceExternalChromeOsKeyboard:
case DeviceType::kDeviceExternalAppleKeyboard:
case DeviceType::kDeviceExternalGenericKeyboard:
case ui::KeyboardCapability::DeviceType::
kDeviceExternalNullTopRowChromeOsKeyboard:
case DeviceType::kDeviceExternalUnknown:
case DeviceType::kDeviceHotrodRemote:
case DeviceType::kDeviceVirtualCoreKeyboard:
if (!priority_keyboard || keyboard.id > priority_keyboard->id) {
priority_keyboard = keyboard;
}
break;
}
}
return priority_keyboard;
}
std::optional<ui::KeyboardDevice> GetInternalKeyboard() {
for (const ui::KeyboardDevice& keyboard :
ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
// If the input device settings controlled does not recognize the device as
// a keyboard, skip it.
if (features::IsInputDeviceSettingsSplitEnabled() &&
Shell::Get()->input_device_settings_controller()->GetKeyboardSettings(
keyboard.id) == nullptr) {
continue;
}
const auto device_type =
Shell::Get()->keyboard_capability()->GetDeviceType(keyboard);
switch (device_type) {
case DeviceType::kDeviceUnknown:
case DeviceType::kDeviceInternalKeyboard:
case DeviceType::kDeviceInternalRevenKeyboard:
return keyboard;
case DeviceType::kDeviceExternalChromeOsKeyboard:
case DeviceType::kDeviceExternalAppleKeyboard:
case DeviceType::kDeviceExternalGenericKeyboard:
case ui::KeyboardCapability::DeviceType::
kDeviceExternalNullTopRowChromeOsKeyboard:
case DeviceType::kDeviceExternalUnknown:
case DeviceType::kDeviceHotrodRemote:
case DeviceType::kDeviceVirtualCoreKeyboard:
break;
}
}
return std::nullopt;
}
// Identifies media keys which exist only on external keyboards.
bool IsMediaKey(ui::KeyboardCode key_code) {
static constexpr auto kMediaKeyCodes =
base::MakeFixedFlatSet<ui::KeyboardCode>(
{ui::VKEY_MEDIA_PAUSE, ui::VKEY_MEDIA_PLAY,
ui::VKEY_OEM_103, // Media Rewind
ui::VKEY_OEM_104, // Media Fast Forward
ui::VKEY_MEDIA_STOP});
return kMediaKeyCodes.contains(key_code);
}
// Some `TopRowActionKey` values must always be shown if there is an external
// keyboard even if the top row of the keyboard does not technically support
// them. This is because many of these keys are common on external keyboards and
// are unable to be deduced properly.
bool ShouldAlwaysShowWithExternalKeyboard(ui::TopRowActionKey action_key) {
switch (action_key) {
case ui::TopRowActionKey::kNone:
case ui::TopRowActionKey::kUnknown:
case ui::TopRowActionKey::kBack:
case ui::TopRowActionKey::kForward:
case ui::TopRowActionKey::kRefresh:
case ui::TopRowActionKey::kKeyboardBacklightToggle:
case ui::TopRowActionKey::kPrivacyScreenToggle:
case ui::TopRowActionKey::kAllApplications:
case ui::TopRowActionKey::kAccessibility:
return false;
case ui::TopRowActionKey::kDictation:
case ui::TopRowActionKey::kFullscreen:
case ui::TopRowActionKey::kOverview:
case ui::TopRowActionKey::kScreenBrightnessDown:
case ui::TopRowActionKey::kScreenBrightnessUp:
case ui::TopRowActionKey::kKeyboardBacklightDown:
case ui::TopRowActionKey::kKeyboardBacklightUp:
case ui::TopRowActionKey::kMicrophoneMute:
case ui::TopRowActionKey::kVolumeMute:
case ui::TopRowActionKey::kVolumeDown:
case ui::TopRowActionKey::kVolumeUp:
case ui::TopRowActionKey::kNextTrack:
case ui::TopRowActionKey::kPreviousTrack:
case ui::TopRowActionKey::kPlayPause:
case ui::TopRowActionKey::kScreenshot:
case ui::TopRowActionKey::kEmojiPicker:
return true;
}
}
bool MetaFKeyRewritesAreSuppressed(const ui::InputDevice& keyboard) {
if (!features::IsInputDeviceSettingsSplitEnabled()) {
return false;
}
const auto* settings =
Shell::Get()->input_device_settings_controller()->GetKeyboardSettings(
keyboard.id);
return settings && settings->suppress_meta_fkey_rewrites;
}
bool AreTopRowFKeys(const ui::InputDevice& keyboard) {
if (!features::IsInputDeviceSettingsSplitEnabled()) {
PrefService* pref_service =
Shell::Get()->session_controller()->GetActivePrefService();
return pref_service && pref_service->GetBoolean(prefs::kSendFunctionKeys);
}
const auto* settings =
Shell::Get()->input_device_settings_controller()->GetKeyboardSettings(
keyboard.id);
return settings && settings->top_row_are_fkeys;
}
bool ShouldShowExternalTopRowActionKeyAlias(
const ui::KeyboardDevice& keyboard,
ui::TopRowActionKey action_key,
const ui::Accelerator& accelerator) {
const bool should_show_action_key =
Shell::Get()->keyboard_capability()->HasTopRowActionKey(keyboard,
action_key) ||
ShouldAlwaysShowWithExternalKeyboard(action_key);
// If Meta + F-Key rewrites are suppressed for the priority keyboard and
// the accelerator contains the search key, we should not show the
// accelerator.
const bool alias_is_suppressed =
accelerator.IsCmdDown() && MetaFKeyRewritesAreSuppressed(keyboard);
return should_show_action_key && !alias_is_suppressed;
}
ui::mojom::SixPackShortcutModifier GetSixPackShortcutModifier(
ui::KeyboardCode key_code,
std::optional<int> device_id) {
if (!features::IsAltClickAndSixPackCustomizationEnabled() ||
!device_id.has_value()) {
return ui::mojom::SixPackShortcutModifier::kSearch;
}
CHECK(ui::KeyboardCapability::IsSixPackKey(key_code));
const auto* settings =
Shell::Get()->input_device_settings_controller()->GetKeyboardSettings(
device_id.value());
if (!settings) {
return ui::mojom::SixPackShortcutModifier::kSearch;
}
switch (key_code) {
case ui::VKEY_DELETE:
return settings->six_pack_key_remappings->del;
case ui::VKEY_INSERT:
return settings->six_pack_key_remappings->insert;
case ui::VKEY_HOME:
return settings->six_pack_key_remappings->home;
case ui::VKEY_END:
return settings->six_pack_key_remappings->end;
case ui::VKEY_PRIOR:
return settings->six_pack_key_remappings->page_up;
case ui::VKEY_NEXT:
return settings->six_pack_key_remappings->page_down;
default:
NOTREACHED();
}
}
ui::mojom::ExtendedFkeysModifier GetExtendedFkeysModifier(
ui::KeyboardCode key_code,
std::optional<int> device_id) {
if (!features::IsInputDeviceSettingsSplitEnabled() ||
!::features::AreF11AndF12ShortcutsEnabled() || !device_id.has_value() ||
!ui::KeyboardCapability::IsF11OrF12(key_code)) {
return ui::mojom::ExtendedFkeysModifier::kDisabled;
}
auto* controller = Shell::Get()->input_device_settings_controller();
CHECK(controller);
const auto* settings = controller->GetKeyboardSettings(device_id.value());
// Settings are only supported for F11 and F12.
if (!settings) {
return ui::mojom::ExtendedFkeysModifier::kDisabled;
}
if (key_code == ui::VKEY_F11) {
return settings->f11.value();
}
return settings->f12.value();
}
bool HasRightAltKeyViaModifierRemapping(const ui::KeyboardDevice& keyboard) {
if (!features::IsInputDeviceSettingsSplitEnabled()) {
return false;
}
auto* settings =
Shell::Get()->input_device_settings_controller()->GetKeyboardSettings(
keyboard.id);
if (!settings) {
return false;
}
bool has_right_alt_key = false;
for (const auto& [_, to] : settings->modifier_remappings) {
if (to == ui::mojom::ModifierKey::kRightAlt) {
has_right_alt_key = true;
break;
}
}
return has_right_alt_key;
}
} // namespace
std::vector<ui::Accelerator> AcceleratorAliasConverter::CreateAcceleratorAlias(
const ui::Accelerator& accelerator) const {
std::optional<ui::KeyboardDevice> priority_external_keyboard =
GetPriorityExternalKeyboard();
std::optional<ui::KeyboardDevice> internal_keyboard = GetInternalKeyboard();
std::optional<int> device_id = std::nullopt;
if (priority_external_keyboard.has_value()) {
device_id = priority_external_keyboard->id;
} else if (internal_keyboard.has_value()) {
device_id = internal_keyboard->id;
}
// If the external and internal keyboards are either both non-chromeos
// keyboards (ex ChromeOS flex devices) or if they are both ChromeOS keyboards
// (ex ChromeOS external keyboard), do not show aliases for the internal
// keyboard.
if (priority_external_keyboard && internal_keyboard &&
(IsChromeOSKeyboard(*priority_external_keyboard) ==
IsChromeOSKeyboard(*internal_keyboard))) {
internal_keyboard = std::nullopt;
}
// Set is used to get rid of possible duplicate accelerators.
base::flat_set<ui::Accelerator> aliases_set;
// Generate aliases for both the priority external keyboard + the internal
// keyboard for top row action keys.
if (priority_external_keyboard) {
if (const auto alias =
CreateTopRowAliases(*priority_external_keyboard, accelerator);
alias) {
aliases_set.insert(*alias);
// Always add the original accelerator if an external keyboard is present.
aliases_set.insert(accelerator);
}
}
if (internal_keyboard) {
if (const auto alias = CreateTopRowAliases(*internal_keyboard, accelerator);
alias) {
aliases_set.insert(*alias);
}
}
if (!aliases_set.empty()) {
return FilterAliasBySupportedKeys(std::move(aliases_set).extract());
}
// Generate aliases for both the priority external keyboard + the internal
// keyboard for CapsLock key.
if (priority_external_keyboard) {
if (const auto alias =
CreateCapsLockAliases(*priority_external_keyboard, accelerator);
alias) {
aliases_set.insert(*alias);
// Always add the original accelerator if an external keyboard is present.
aliases_set.insert(accelerator);
}
}
if (internal_keyboard) {
if (const auto alias =
CreateCapsLockAliases(*internal_keyboard, accelerator);
alias) {
aliases_set.insert(*alias);
// Always add the original accelerator if an internal keyboard is present.
aliases_set.insert(accelerator);
}
}
if (!aliases_set.empty()) {
return FilterAliasBySupportedKeys(std::move(aliases_set).extract());
}
// Generate aliases for both the priority external keyboard + the internal
// keyboard for f-keys.
if (priority_external_keyboard) {
if (const auto alias =
CreateFunctionKeyAliases(*priority_external_keyboard, accelerator);
alias) {
aliases_set.insert(*alias);
}
}
if (internal_keyboard) {
if (const auto alias =
CreateFunctionKeyAliases(*internal_keyboard, accelerator);
alias) {
aliases_set.insert(*alias);
}
if (ui::KeyboardCapability::IsF11OrF12(accelerator.key_code()) &&
Shell::Get()->keyboard_capability()->IsChromeOSKeyboard(
internal_keyboard->id)) {
if (const auto alias = CreateExtendedFKeysAliases(
*internal_keyboard, accelerator, internal_keyboard->id);
alias) {
aliases_set.insert(*alias);
// If there is an external keyboard connected, we show both versions of
// the shortcut (base accelerator + alias).
if (priority_external_keyboard) {
aliases_set.insert(accelerator);
}
}
}
}
if (!aliases_set.empty()) {
return FilterAliasBySupportedKeys(std::move(aliases_set).extract());
}
// For |six_pack_key|, show both the base
// accelerator and the remapped accelerator if applicable. Otherwise, only
// show base accelerator.
std::vector<ui::Accelerator> aliases =
CreateSixPackAliases(accelerator, device_id);
// Add base accelerator.
aliases.push_back(accelerator);
return FilterAliasBySupportedKeys(aliases);
}
std::optional<ui::Accelerator>
AcceleratorAliasConverter::CreateFunctionKeyAliases(
const ui::KeyboardDevice& keyboard,
const ui::Accelerator& accelerator) const {
// Avoid remapping if [Search] is part of the original accelerator.
if (accelerator.IsCmdDown()) {
return std::nullopt;
}
// Only attempt to alias if the provided accelerator is for an F-Key.
if (accelerator.key_code() < ui::VKEY_F1 ||
accelerator.key_code() > ui::VKEY_F24) {
return std::nullopt;
}
// Attempt to get the corresponding `ui::TopRowActionKey` for the given F-Key.
std::optional<ui::TopRowActionKey> action_key =
Shell::Get()->keyboard_capability()->GetCorrespondingActionKeyForFKey(
keyboard, accelerator.key_code());
if (!action_key) {
return std::nullopt;
}
// Convert the `ui::TopRowActionKey` to the corresponding `ui::KeyboardCode`
std::optional<ui::KeyboardCode> action_vkey =
ui::KeyboardCapability::ConvertToKeyboardCode(*action_key);
if (!action_vkey) {
return std::nullopt;
}
const bool top_row_are_fkeys = AreTopRowFKeys(keyboard);
if (IsSplitModifierKeyboard(keyboard.id)) {
// If its a split modifier Keyboard, the UI should show the Action Key
// glyph. If `top_row_are_fkeys` is false, function key must be added so
// convert the "F-Key" into the action key.
if (top_row_are_fkeys) {
return {ui::Accelerator(*action_vkey, accelerator.modifiers(),
accelerator.key_state())};
} else {
return {ui::Accelerator(*action_vkey,
accelerator.modifiers() | ui::EF_FUNCTION_DOWN,
accelerator.key_state())};
}
} else if (IsChromeOSKeyboard(keyboard)) {
// If `priority_keyboard` is a ChromeOS keyboard, the UI should show the
// corresponding action key, the the F-Key glyph.
if (top_row_are_fkeys) {
return {ui::Accelerator(*action_vkey, accelerator.modifiers(),
accelerator.key_state())};
} else {
return {ui::Accelerator(*action_vkey,
accelerator.modifiers() | ui::EF_COMMAND_DOWN,
accelerator.key_state())};
}
} else {
// On a non-chromeos keyboard, UI should show the F-Key instead.
if (top_row_are_fkeys) {
return {accelerator};
} else {
return {ui::Accelerator(accelerator.key_code(),
accelerator.modifiers() | ui::EF_COMMAND_DOWN,
accelerator.key_state())};
}
}
}
std::optional<ui::Accelerator>
AcceleratorAliasConverter::CreateExtendedFKeysAliases(
const ui::KeyboardDevice& keyboard,
const ui::Accelerator& accelerator,
std::optional<int> device_id) const {
if (!::features::AreF11AndF12ShortcutsEnabled() ||
!ui::KeyboardCapability::IsF11OrF12(accelerator.key_code()) ||
!device_id.has_value()) {
return std::nullopt;
}
const ui::KeyboardCode accel_key_code = accelerator.key_code();
const ui::mojom::ExtendedFkeysModifier fkey_modifier =
GetExtendedFkeysModifier(accel_key_code, device_id);
int modifiers;
const bool top_row_are_fkeys = AreTopRowFKeys(keyboard);
bool avoid_remapping = false;
switch (fkey_modifier) {
case ui::mojom::ExtendedFkeysModifier::kDisabled:
avoid_remapping = true;
break;
case ui::mojom::ExtendedFkeysModifier::kAlt:
avoid_remapping = accelerator.IsAltDown();
modifiers = ui::EF_ALT_DOWN;
break;
case ui::mojom::ExtendedFkeysModifier::kShift:
avoid_remapping = accelerator.IsShiftDown();
modifiers = ui::EF_SHIFT_DOWN;
break;
case ui::mojom::ExtendedFkeysModifier::kCtrlShift:
avoid_remapping = accelerator.IsShiftDown() || accelerator.IsCtrlDown();
modifiers = (ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN);
break;
}
if (avoid_remapping) {
return std::nullopt;
}
ui::KeyboardCode key_code;
const auto* top_row_keys =
Shell::Get()->keyboard_capability()->GetTopRowActionKeys(keyboard);
if (!top_row_keys) {
return std::nullopt;
}
const int top_row_key_index = accel_key_code - ui::KeyboardCode::VKEY_F11;
CHECK(0 <= top_row_key_index && top_row_key_index <= 1);
key_code = Shell::Get()
->keyboard_capability()
->ConvertToKeyboardCode((*top_row_keys)[top_row_key_index])
.value();
modifiers = accelerator.modifiers() |
(top_row_are_fkeys ? modifiers : modifiers | ui::EF_COMMAND_DOWN);
return ui::Accelerator(key_code, modifiers);
}
std::optional<ui::Accelerator> AcceleratorAliasConverter::CreateCapsLockAliases(
const ui::KeyboardDevice& keyboard,
const ui::Accelerator& accelerator) const {
if (accelerator.key_code() != ui::VKEY_CAPITAL) {
return std::nullopt;
}
if (Shell::Get()->keyboard_capability()->HasFunctionKey(keyboard)) {
return {ui::Accelerator(ui::VKEY_RIGHT_ALT, ui::EF_FUNCTION_DOWN)};
}
return accelerator;
}
std::optional<ui::Accelerator> AcceleratorAliasConverter::CreateTopRowAliases(
const ui::KeyboardDevice& keyboard,
const ui::Accelerator& accelerator) const {
// Avoid remapping if [Search] is part of the original accelerator.
if (accelerator.IsCmdDown()) {
return std::nullopt;
}
// If the accelerator is not an action key, do no aliasing.
std::optional<ui::TopRowActionKey> action_key =
ui::KeyboardCapability::ConvertToTopRowActionKey(accelerator.key_code());
if (!action_key) {
return std::nullopt;
}
std::optional<ui::KeyboardCode> function_key =
Shell::Get()->keyboard_capability()->GetCorrespondingFunctionKey(
keyboard, *action_key);
if (!function_key.has_value()) {
return std::nullopt;
}
const bool top_row_are_fkeys = AreTopRowFKeys(keyboard);
if (IsSplitModifierKeyboard(keyboard.id)) {
// If its a split modifier Keyboard, the UI should show the Action Key
// glyph. If `top_row_are_fkeys` is true, function key must be added so
// convert the "F-Key" into the action key.
if (top_row_are_fkeys) {
return {ui::Accelerator(accelerator.key_code(),
accelerator.modifiers() | ui::EF_FUNCTION_DOWN,
accelerator.key_state())};
} else {
// Otherwise if `top_row_are_fkeys` is false, the identity accelerator
// should be returned.
return {accelerator};
}
} else if (IsChromeOSKeyboard(keyboard)) {
// If its a ChromeOS Keyboard, the UI should show the Action Key glyph. If
// `top_row_are_fkeys` is true, Search must be added so convert the "F-Key"
// into the action key.
if (top_row_are_fkeys) {
return {ui::Accelerator(accelerator.key_code(),
accelerator.modifiers() | ui::EF_COMMAND_DOWN,
accelerator.key_state())};
} else {
// Otherwise if `top_row_are_fkeys` is false, the identity accelerator
// should be returned.
return {accelerator};
}
} else {
// If its an external, the F-Key glyph should be shown. If
// `top_row_are_fkeys` is true, search must be added to convert the "F-Key"
// into the action key. Otherwise, the "F-Key" is implicitly the action key.
if (top_row_are_fkeys) {
return {ui::Accelerator(*function_key,
accelerator.modifiers() | ui::EF_COMMAND_DOWN,
accelerator.key_state())};
} else {
return {ui::Accelerator(*function_key, accelerator.modifiers(),
accelerator.key_state())};
}
}
}
std::vector<ui::Accelerator> AcceleratorAliasConverter::CreateSixPackAliases(
const ui::Accelerator& accelerator,
std::optional<int> device_id) const {
if (!::features::IsImprovedKeyboardShortcutsEnabled() ||
!ui::KeyboardCapability::IsSixPackKey(accelerator.key_code())) {
return std::vector<ui::Accelerator>();
}
if (features::IsAltClickAndSixPackCustomizationEnabled() &&
!device_id.has_value()) {
return std::vector<ui::Accelerator>();
}
if (device_id.has_value() && IsSplitModifierKeyboard(device_id.value())) {
const auto iter = ui::kSixPackKeyToFnKeyMap.find(accelerator.key_code());
// [Insert] is technically a six pack key but has no Fn based rewrite. Need
// to make sure we return no aliased accelerator for this case.
if (iter == ui::kSixPackKeyToFnKeyMap.end()) {
return std::vector<ui::Accelerator>();
}
return {ui::Accelerator(iter->second,
accelerator.modifiers() | ui::EF_FUNCTION_DOWN,
accelerator.key_state())};
}
// Edge cases:
// 1. [Shift] + [Delete] should not be remapped to [Shift] + [Search] +
// [Back] (aka, Insert).
// 2. For [Insert], avoid remapping if [Shift] is part of original
// accelerator.
if (accelerator.IsShiftDown() &&
(accelerator.key_code() == ui::KeyboardCode::VKEY_DELETE ||
accelerator.key_code() == ui::KeyboardCode::VKEY_INSERT)) {
return std::vector<ui::Accelerator>();
}
const ui::KeyboardCode accel_key_code = accelerator.key_code();
const ui::mojom::SixPackShortcutModifier six_pack_shortcut_modifier =
GetSixPackShortcutModifier(accel_key_code, device_id);
if (six_pack_shortcut_modifier == ui::mojom::SixPackShortcutModifier::kNone) {
return std::vector<ui::Accelerator>();
}
// For all |six_pack_keys|, avoid remapping if the six-pack remap modifier
// (Search or Alt) is part of the original accelerator.
if (ui::mojom::SixPackShortcutModifier::kSearch ==
six_pack_shortcut_modifier &&
accelerator.IsCmdDown()) {
return std::vector<ui::Accelerator>();
}
if (ui::mojom::SixPackShortcutModifier::kAlt == six_pack_shortcut_modifier &&
accelerator.IsAltDown()) {
return std::vector<ui::Accelerator>();
}
// For the Home and End Alt-based aliases, they additionally include the
// Ctrl modifier, so the original accelerator must not include Ctrl.
if (ui::mojom::SixPackShortcutModifier::kAlt == six_pack_shortcut_modifier &&
accelerator.IsCtrlDown() &&
(accel_key_code == ui::VKEY_HOME || accel_key_code == ui::VKEY_END)) {
return std::vector<ui::Accelerator>();
}
int modifiers;
ui::KeyboardCode key_code;
if (six_pack_shortcut_modifier ==
ui::mojom::SixPackShortcutModifier::kSearch) {
key_code = ui::kSixPackKeyToSearchSystemKeyMap.at(accel_key_code);
modifiers = ui::EF_COMMAND_DOWN;
} else {
key_code = ui::kSixPackKeyToAltSystemKeyMap.at(accel_key_code);
modifiers = (accel_key_code == ui::KeyboardCode::VKEY_END ||
accel_key_code == ui::KeyboardCode::VKEY_HOME)
? (ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN)
: ui::EF_ALT_DOWN;
}
// For Insert: [modifiers] = [Search] + [Shift] + [original_modifiers].
// For other |six_pack_keys|:
// [modifiers] = [Search/Alt] + [original_modifiers].
int updated_modifiers =
accel_key_code == ui::KeyboardCode::VKEY_INSERT
? accelerator.modifiers() | ui::EF_COMMAND_DOWN | ui::EF_SHIFT_DOWN
: accelerator.modifiers() | modifiers;
return {
ui::Accelerator(key_code, updated_modifiers, accelerator.key_state())};
}
std::vector<ui::Accelerator>
AcceleratorAliasConverter::FilterAliasBySupportedKeys(
const std::vector<ui::Accelerator>& accelerators) const {
const auto* keyboard_capability = Shell::Get()->keyboard_capability();
CHECK(keyboard_capability);
std::vector<ui::Accelerator> filtered_accelerators;
auto priority_keyboard = GetPriorityExternalKeyboard();
auto internal_keyboard = GetInternalKeyboard();
// If the external and internal keyboards are either both non-chromeos
// keyboards (ex ChromeOS flex devices) or if they are both ChromeOS keyboards
// (ex ChromeOS external keyboard), do not show aliases for the internal
// keyboard.
if (priority_keyboard && internal_keyboard &&
(IsChromeOSKeyboard(*priority_keyboard) ==
IsChromeOSKeyboard(*internal_keyboard))) {
internal_keyboard = std::nullopt;
}
for (const auto& accelerator : accelerators) {
// TODO(dpad): Handle privacy screen correctly. This can be simplified once
// drallion devices are properly handled in `KeyboardCapability`.
if (auto action_key = ui::KeyboardCapability::ConvertToTopRowActionKey(
accelerator.key_code());
action_key.has_value() &&
action_key != ui::TopRowActionKey::kPrivacyScreenToggle) {
// Accelerator should only be seen if the priority or internal keyboard
// have the key OR if there is an external keyboard and the `action_key`
// should always been shown when there is an external keyboard.
if (priority_keyboard &&
(ShouldShowExternalTopRowActionKeyAlias(*priority_keyboard,
*action_key, accelerator))) {
filtered_accelerators.push_back(accelerator);
} else if (internal_keyboard && keyboard_capability->HasTopRowActionKey(
*internal_keyboard, *action_key)) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
// If the accelerator is for an FKey + Search, make sure it is only shown if
// Meta + F-Key rewrites are allowed.
if (accelerator.key_code() > ui::VKEY_F1 &&
accelerator.key_code() < ui::VKEY_F24 && accelerator.IsCmdDown()) {
if (priority_keyboard &&
!MetaFKeyRewritesAreSuppressed(*priority_keyboard)) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (ui::KeyboardCapability::IsSixPackKey(accelerator.key_code())) {
if (ui::KeyboardCapability::HasSixPackOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (accelerator.key_code() == ui::VKEY_ASSISTANT) {
if (priority_keyboard &&
keyboard_capability->HasAssistantKey(*priority_keyboard)) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (accelerator.key_code() == ui::VKEY_MODECHANGE) {
if (keyboard_capability->HasGlobeKeyOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
// VKEY_MEDIA_LAUNCH_APP2 is the "Calculator" button on many external
// keyboards.
if (accelerator.key_code() == ui::VKEY_MEDIA_LAUNCH_APP2) {
if (keyboard_capability->HasCalculatorKeyOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (accelerator.key_code() == ui::VKEY_PRIVACY_SCREEN_TOGGLE) {
if (Shell::Get()->privacy_screen_controller()->IsSupported()) {
for (const ui::KeyboardDevice& keyboard :
ui::DeviceDataManager::GetInstance()->GetKeyboardDevices()) {
if (keyboard_capability->GetDeviceType(keyboard) ==
ui::KeyboardCapability::DeviceType::kDeviceInternalKeyboard) {
filtered_accelerators.push_back(accelerator);
break;
}
}
}
continue;
}
if (accelerator.key_code() == ui::VKEY_BROWSER_SEARCH) {
if (keyboard_capability->HasBrowserSearchKeyOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (IsMediaKey(accelerator.key_code())) {
if (keyboard_capability->HasMediaKeysOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (accelerator.key_code() == ui::VKEY_SETTINGS) {
if (keyboard_capability->HasSettingsKeyOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (accelerator.key_code() == ui::VKEY_HELP) {
if (keyboard_capability->HasHelpKeyOnAnyKeyboard()) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
// If the accelerator is pressing Search + Alt to do capslock, only Alt +
// Search should be shown in the shortcuts app.
if (accelerator.key_code() == ui::VKEY_MENU &&
accelerator.modifiers() == ui::EF_COMMAND_DOWN) {
continue;
}
// Add [CapsLock] to the accelerators list if keyboard has CapsLock.
if (accelerator.key_code() == ui::VKEY_CAPITAL) {
if ((priority_keyboard &&
keyboard_capability->HasCapsLockKey(*priority_keyboard)) ||
(internal_keyboard &&
keyboard_capability->HasCapsLockKey(*internal_keyboard))) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
// Add Alt + Search to the accelerators list.
if (accelerator.key_code() == ui::VKEY_LWIN &&
accelerator.modifiers() == ui::EF_ALT_DOWN) {
if ((internal_keyboard &&
!IsSplitModifierKeyboard(internal_keyboard->id)) ||
priority_keyboard) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
if (accelerator.key_code() == ui::VKEY_RIGHT_ALT) {
if (internal_keyboard && IsSplitModifierKeyboard(internal_keyboard->id)) {
filtered_accelerators.push_back(accelerator);
} else if ((internal_keyboard &&
HasRightAltKeyViaModifierRemapping(*internal_keyboard)) ||
(priority_keyboard &&
HasRightAltKeyViaModifierRemapping(*priority_keyboard))) {
filtered_accelerators.push_back(accelerator);
}
continue;
}
// Otherwise, always copy the accelerator.
filtered_accelerators.push_back(accelerator);
}
return filtered_accelerators;
}
} // namespace ash