// Copyright 2022 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/ash/app_list/search/keyboard_shortcut_result.h"
#include <cstddef>
#include <string>
#include <string_view>
#include <vector>
#include "ash/accelerators/keyboard_code_util.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/app_list/app_list_types.h"
#include "ash/public/mojom/accelerator_info.mojom-shared.h"
#include "ash/public/mojom/accelerator_info.mojom.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/webui/shortcut_customization_ui/backend/search/search.mojom.h"
#include "base/check.h"
#include "base/containers/fixed_flat_map.h"
#include "base/i18n/rtl.h"
#include "base/notreached.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/ash/app_list/search/common/icon_constants.h"
#include "chrome/browser/ash/app_list/search/common/search_result_util.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chromeos/ash/components/string_matching/fuzzy_tokenized_string_match.h"
#include "chromeos/ash/components/string_matching/tokenized_string.h"
#include "chromeos/ash/components/string_matching/tokenized_string_match.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "chromeos/ui/vector_icons/vector_icons.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/ash/keyboard_capability.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "chromeos/ash/resources/internal/strings/grit/ash_internal_strings.h"
#endif // BUILDFLAG(GOOGLE_CHROME_BRANDING)
namespace app_list {
namespace {
using ::ash::string_matching::FuzzyTokenizedStringMatch;
using ::ash::string_matching::TokenizedString;
using ::ash::string_matching::TokenizedStringMatch;
using TextVector = ChromeSearchResult::TextVector;
using IconCode = ::ash::SearchResultTextItem::IconCode;
using ::ui::KeyboardCode;
constexpr char kKeyboardShortcutScheme[] = "keyboard_shortcut://";
// The icon labels used by the shortcuts app can be found here:
// https://crsrc.org/c/ash/webui/shortcut_customization_ui/shortcut_customization_app_ui.cc;l=125.
std::optional<int> GetStringIdForIconCode(IconCode icon_code) {
switch (icon_code) {
case ash::SearchResultTextItem::kKeyboardShortcutPower:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_POWER;
case ash::SearchResultTextItem::kKeyboardShortcutKeyboardBacklightToggle:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_KEYBOARD_BACKLIGHT_TOGGLE;
case ash::SearchResultTextItem::kKeyboardShortcutMicrophone:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MICROPHONE_MUTE_TOGGLE;
case ash::SearchResultTextItem::kKeyboardShortcutAssistant:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_LAUNCH_ASSISTANT;
case ash::SearchResultTextItem::kKeyboardShortcutAllApps:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_VIEW_ALL_APPS;
case ash::SearchResultTextItem::kKeyboardShortcutBrowserBack:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_BROWSER_BACK;
case ash::SearchResultTextItem::kKeyboardShortcutBrowserForward:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_BROWSER_FORWARD;
case ash::SearchResultTextItem::kKeyboardShortcutBrowserRefresh:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_BROWSER_REFRESH;
case ash::SearchResultTextItem::kKeyboardShortcutBrowserSearch:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_BROWSER_SEARCH;
case ash::SearchResultTextItem::kKeyboardShortcutCalculator:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_LAUNCH_APPLICATION2;
case ash::SearchResultTextItem::kKeyboardShortcutDictationToggle:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_ENABLE_OR_TOGGLE_DICTATION;
case ash::SearchResultTextItem::kKeyboardShortcutEmojiPicker:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_EMOJI_PICKER;
case ash::SearchResultTextItem::kKeyboardShortcutInputModeChange:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MODE_CHANGE;
case ash::SearchResultTextItem::kKeyboardShortcutZoom:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_ZOOM_TOGGLE;
case ash::SearchResultTextItem::kKeyboardShortcutMediaLaunchApp1:
case ash::SearchResultTextItem::kKeyboardShortcutMediaLaunchApp1Refresh:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_LAUNCH_APPLICATION1;
case ash::SearchResultTextItem::kKeyboardShortcutMediaFastForward:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MEDIA_FAST_FORWARD;
case ash::SearchResultTextItem::kKeyboardShortcutMediaPause:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MEDIA_PAUSE;
case ash::SearchResultTextItem::kKeyboardShortcutMediaPlay:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MEDIA_PLAY;
case ash::SearchResultTextItem::kKeyboardShortcutMediaPlayPause:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MEDIA_PLAY_PAUSE;
case ash::SearchResultTextItem::kKeyboardShortcutMediaTrackNext:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MEDIA_TRACK_NEXT;
case ash::SearchResultTextItem::kKeyboardShortcutMediaTrackPrevious:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_MEDIA_TRACK_PREVIOUS;
case ash::SearchResultTextItem::kKeyboardShortcutBrightnessDown:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_KEYBOARD_BRIGHTNESS_DOWN;
case ash::SearchResultTextItem::kKeyboardShortcutKeyboardBrightnessDown:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_KEYBOARD_BRIGHTNESS_DOWN;
case ash::SearchResultTextItem::kKeyboardShortcutBrightnessUp:
case ash::SearchResultTextItem::kKeyboardShortcutBrightnessUpRefresh:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_BRIGHTNESS_UP;
case ash::SearchResultTextItem::kKeyboardShortcutKeyboardBrightnessUp:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_KEYBOARD_BRIGHTNESS_UP;
case ash::SearchResultTextItem::kKeyboardShortcutVolumeMute:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_AUDIO_VOLUME_MUTE;
case ash::SearchResultTextItem::kKeyboardShortcutVolumeDown:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_AUDIO_VOLUME_DOWN;
case ash::SearchResultTextItem::kKeyboardShortcutVolumeUp:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_AUDIO_VOLUME_UP;
case ash::SearchResultTextItem::kKeyboardShortcutUp:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_ARROW_UP;
case ash::SearchResultTextItem::kKeyboardShortcutDown:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_ARROW_DOWN;
case ash::SearchResultTextItem::kKeyboardShortcutLeft:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_ARROW_LEFT;
case ash::SearchResultTextItem::kKeyboardShortcutRight:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_ARROW_RIGHT;
case ash::SearchResultTextItem::kKeyboardShortcutPrivacyScreenToggle:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_PRIVACY_SCREEN_TOGGLE;
case ash::SearchResultTextItem::kKeyboardShortcutSettings:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_SETTINGS;
case ash::SearchResultTextItem::kKeyboardShortcutSnapshot:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_PRINT_SCREEN;
case ash::SearchResultTextItem::kKeyboardShortcutLauncher:
case ash::SearchResultTextItem::kKeyboardShortcutLauncherRefresh:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_OPEN_LAUNCHER;
case ash::SearchResultTextItem::kKeyboardShortcutSearch:
return IDS_SHORTCUT_CUSTOMIZATION_ICON_LABEL_OPEN_SEARCH;
case ash::SearchResultTextItem::kKeyboardShortcutKeyboardRightAlt:
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
return IDS_KEYBOARD_RIGHT_ALT_LABEL;
#else
return IDS_SHORTCUT_CUSTOMIZATION_INPUT_KEY_PLACEHOLDER;
#endif
}
}
std::u16string GetAccessibleStringForIcon(IconCode icon_code) {
const std::optional<int> icon_code_string_id =
GetStringIdForIconCode(icon_code);
CHECK(icon_code_string_id.has_value());
return l10n_util::GetStringFUTF16(
IDS_SHORTCUT_CUSTOMIZATION_ARIA_LABEL_FOR_A_KEY,
l10n_util::GetStringUTF16(icon_code_string_id.value()));
}
std::u16string GetAccessibleStringForKey(const std::u16string& key_string) {
return l10n_util::GetStringFUTF16(
IDS_SHORTCUT_CUSTOMIZATION_ARIA_LABEL_FOR_A_KEY, key_string);
}
bool IsModifierKey(ui::KeyboardCode keycode) {
switch (keycode) {
case ui::KeyboardCode::VKEY_COMMAND:
case ui::KeyboardCode::VKEY_LMENU:
case ui::KeyboardCode::VKEY_RCONTROL:
case ui::KeyboardCode::VKEY_CONTROL:
case ui::KeyboardCode::VKEY_SHIFT:
return true;
default:
return false;
}
}
} // namespace
std::optional<IconCode> KeyboardShortcutResult::GetIconCodeFromKeyboardCode(
KeyboardCode keyboard_code) {
switch (keyboard_code) {
case (KeyboardCode::VKEY_BROWSER_BACK):
return IconCode::kKeyboardShortcutBrowserBack;
case (KeyboardCode::VKEY_BROWSER_FORWARD):
return IconCode::kKeyboardShortcutBrowserForward;
case (KeyboardCode::VKEY_BROWSER_REFRESH):
return IconCode::kKeyboardShortcutBrowserRefresh;
case (KeyboardCode::VKEY_BROWSER_SEARCH):
return IconCode::kKeyboardShortcutBrowserSearch;
case (KeyboardCode::VKEY_DICTATE):
return IconCode::kKeyboardShortcutDictationToggle;
case (KeyboardCode::VKEY_EMOJI_PICKER):
return IconCode::kKeyboardShortcutEmojiPicker;
case (KeyboardCode::VKEY_ZOOM):
return IconCode::kKeyboardShortcutZoom;
case (KeyboardCode::VKEY_MEDIA_LAUNCH_APP1):
return ash::Shell::Get()->keyboard_capability()->UseRefreshedIcons()
? IconCode::kKeyboardShortcutMediaLaunchApp1Refresh
: IconCode::kKeyboardShortcutMediaLaunchApp1;
case (KeyboardCode::VKEY_MEDIA_NEXT_TRACK):
return IconCode::kKeyboardShortcutMediaTrackNext;
case (KeyboardCode::VKEY_MEDIA_PREV_TRACK):
return IconCode::kKeyboardShortcutMediaTrackPrevious;
case (KeyboardCode::VKEY_MEDIA_PLAY):
return IconCode::kKeyboardShortcutMediaPlay;
case (KeyboardCode::VKEY_MEDIA_PAUSE):
return IconCode::kKeyboardShortcutMediaPause;
case (KeyboardCode::VKEY_MEDIA_PLAY_PAUSE):
return IconCode::kKeyboardShortcutMediaPlayPause;
case (KeyboardCode::VKEY_KBD_BACKLIGHT_TOGGLE):
return IconCode::kKeyboardShortcutKeyboardBacklightToggle;
case (KeyboardCode::VKEY_KBD_BRIGHTNESS_DOWN):
return IconCode::kKeyboardShortcutKeyboardBrightnessDown;
case (KeyboardCode::VKEY_KBD_BRIGHTNESS_UP):
return IconCode::kKeyboardShortcutKeyboardBrightnessUp;
case (KeyboardCode::VKEY_OEM_104):
return IconCode::kKeyboardShortcutMediaFastForward;
case (KeyboardCode::VKEY_BRIGHTNESS_DOWN):
return IconCode::kKeyboardShortcutBrightnessDown;
case (KeyboardCode::VKEY_BRIGHTNESS_UP):
return ash::Shell::Get()->keyboard_capability()->UseRefreshedIcons()
? IconCode::kKeyboardShortcutBrightnessUpRefresh
: IconCode::kKeyboardShortcutBrightnessUp;
case (KeyboardCode::VKEY_VOLUME_MUTE):
return IconCode::kKeyboardShortcutVolumeMute;
case (KeyboardCode::VKEY_VOLUME_DOWN):
return IconCode::kKeyboardShortcutVolumeDown;
case (KeyboardCode::VKEY_VOLUME_UP):
return IconCode::kKeyboardShortcutVolumeUp;
case (KeyboardCode::VKEY_UP):
return IconCode::kKeyboardShortcutUp;
case (KeyboardCode::VKEY_DOWN):
return IconCode::kKeyboardShortcutDown;
case (KeyboardCode::VKEY_LEFT):
return IconCode::kKeyboardShortcutLeft;
case (KeyboardCode::VKEY_RIGHT):
return IconCode::kKeyboardShortcutRight;
case (KeyboardCode::VKEY_PRIVACY_SCREEN_TOGGLE):
return IconCode::kKeyboardShortcutPrivacyScreenToggle;
case (KeyboardCode::VKEY_SETTINGS):
return IconCode::kKeyboardShortcutSettings;
case (KeyboardCode::VKEY_SNAPSHOT):
return IconCode::kKeyboardShortcutSnapshot;
case (KeyboardCode::VKEY_LWIN):
case (KeyboardCode::VKEY_RWIN):
// The search and launcher are the same. The icon we display is dependent
// on a best-attempt heuristic on whether the chromebook internal keyboard
// is a launcher or magnifier icon.
switch (ash::Shell::Get()->keyboard_capability()->GetMetaKeyToDisplay()) {
case ui::mojom::MetaKey::kSearch:
return IconCode::kKeyboardShortcutSearch;
case ui::mojom::MetaKey::kLauncher:
return IconCode::kKeyboardShortcutLauncher;
case ui::mojom::MetaKey::kLauncherRefresh:
return IconCode::kKeyboardShortcutLauncherRefresh;
case ui::mojom::MetaKey::kExternalMeta:
case ui::mojom::MetaKey::kCommand:
NOTREACHED();
}
case (KeyboardCode::VKEY_MEDIA_LAUNCH_APP2):
return IconCode::kKeyboardShortcutCalculator;
case (KeyboardCode::VKEY_ALL_APPLICATIONS):
return IconCode::kKeyboardShortcutAllApps;
case (KeyboardCode::VKEY_ASSISTANT):
return IconCode::kKeyboardShortcutAssistant;
case (KeyboardCode::VKEY_MODECHANGE):
return IconCode::kKeyboardShortcutInputModeChange;
case (KeyboardCode::VKEY_MICROPHONE_MUTE_TOGGLE):
return IconCode::kKeyboardShortcutMicrophone;
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
case (KeyboardCode::VKEY_RIGHT_ALT):
return IconCode::kKeyboardShortcutKeyboardRightAlt;
#endif
default:
return std::nullopt;
}
}
// This map matches the `keyToIconNameMap` of the shortcuts app frontend:
// https://crsrc.org/c/ash/webui/shortcut_customization_ui/resources/js/input_key.ts;l=30.
std::optional<ash::SearchResultTextItem::IconCode>
KeyboardShortcutResult::GetIconCodeByKeyString(std::u16string_view key_string) {
static constexpr auto kIconCodes = base::MakeFixedFlatMap<std::u16string_view,
IconCode>(
{{u"ArrowDown", IconCode::kKeyboardShortcutDown},
{u"ArrowLeft", IconCode::kKeyboardShortcutLeft},
{u"ArrowRight", IconCode::kKeyboardShortcutRight},
{u"ArrowUp", IconCode::kKeyboardShortcutUp},
{u"AudioVolumeDown", IconCode::kKeyboardShortcutVolumeDown},
{u"AudioVolumeMute", IconCode::kKeyboardShortcutVolumeMute},
{u"AudioVolumeUp", IconCode::kKeyboardShortcutVolumeUp},
{u"BrightnessDown", IconCode::kKeyboardShortcutBrightnessDown},
{u"BrightnessUp", IconCode::kKeyboardShortcutBrightnessUp},
{u"BrowserBack", IconCode::kKeyboardShortcutBrowserBack},
{u"BrowserForward", IconCode::kKeyboardShortcutBrowserForward},
{u"BrowserRefresh", IconCode::kKeyboardShortcutBrowserRefresh},
{u"BrowserSearch", IconCode::kKeyboardShortcutBrowserSearch},
{u"EmojiPicker", IconCode::kKeyboardShortcutEmojiPicker},
{u"EnableOrToggleDictation", IconCode::kKeyboardShortcutDictationToggle},
{u"KeyboardBacklightToggle",
IconCode::kKeyboardShortcutKeyboardBacklightToggle},
{u"KeyboardBrightnessDown",
IconCode::kKeyboardShortcutKeyboardBrightnessDown},
{u"KeyboardBrightnessUp",
IconCode::kKeyboardShortcutKeyboardBrightnessUp},
{u"LaunchApplication1", IconCode::kKeyboardShortcutMediaLaunchApp1},
{u"LaunchApplication2", IconCode::kKeyboardShortcutCalculator},
{u"LaunchAssistant", IconCode::kKeyboardShortcutAssistant},
{u"MediaFastForward", IconCode::kKeyboardShortcutMediaFastForward},
{u"MediaPause", IconCode::kKeyboardShortcutMediaPause},
{u"MediaPlay", IconCode::kKeyboardShortcutMediaPlay},
{u"MediaPlayPause", IconCode::kKeyboardShortcutMediaPlayPause},
{u"MediaTrackNext", IconCode::kKeyboardShortcutMediaTrackNext},
{u"MediaTrackPrevious", IconCode::kKeyboardShortcutMediaTrackPrevious},
{u"MicrophoneMuteToggle", IconCode::kKeyboardShortcutMicrophone},
{u"ModeChange", IconCode::kKeyboardShortcutInputModeChange},
{u"Power", IconCode::kKeyboardShortcutPower},
{u"PrintScreen", IconCode::kKeyboardShortcutSnapshot},
{u"PrivacyScreenToggle", IconCode::kKeyboardShortcutPrivacyScreenToggle},
{u"RightAlt", IconCode::kKeyboardShortcutKeyboardRightAlt},
{u"Settings", IconCode::kKeyboardShortcutSettings},
{u"ViewAllApps", IconCode::kKeyboardShortcutAllApps},
{u"ZoomToggle", IconCode::kKeyboardShortcutZoom}});
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
static constexpr auto kRefreshIconCodes =
base::MakeFixedFlatMap<std::u16string_view, IconCode>(
{{u"LaunchApplication1",
IconCode::kKeyboardShortcutMediaLaunchApp1Refresh},
{u"BrightnessUp", IconCode::kKeyboardShortcutBrightnessUpRefresh}});
// If there is a refreshed version of the given key, give priority to the new
// icons.
auto it_refresh = kRefreshIconCodes.find(key_string);
if (ash::Shell::Get()->keyboard_capability()->UseRefreshedIcons() &&
it_refresh != kRefreshIconCodes.end()) {
return it_refresh->second;
}
#endif
auto it = kIconCodes.find(key_string);
if (it == kIconCodes.end()) {
return std::nullopt;
}
return it->second;
}
void KeyboardShortcutResult::PopulateTextVector(
TextVector* text_vector,
std::vector<std::u16string>& accessible_strings,
const ui::Accelerator& accelerator) {
CHECK(text_vector);
std::vector<KeyboardCode> key_codes;
// Insert keys by the order of SEARCH, CTRL, ALT, SHIFT, and then key, to be
// consistent with the shortcuts app.
if (accelerator.IsCmdDown()) {
key_codes.push_back(KeyboardCode::VKEY_COMMAND);
}
if (accelerator.IsCtrlDown()) {
key_codes.push_back(KeyboardCode::VKEY_CONTROL);
}
if (accelerator.IsAltDown()) {
key_codes.push_back(KeyboardCode::VKEY_LMENU);
}
if (accelerator.IsShiftDown()) {
key_codes.push_back(KeyboardCode::VKEY_SHIFT);
}
key_codes.push_back(accelerator.key_code());
for (auto key_code : key_codes) {
const std::optional<IconCode> icon_code =
GetIconCodeFromKeyboardCode(key_code);
bool use_alternative_styling = IsModifierKey(key_code);
if (icon_code) {
// The KeyboardCode has a corresponding IconCode, and therefore an
// icon image is supported by the front-end.
ash::SearchResultTextItem result_item =
CreateIconCodeTextItem(icon_code.value());
result_item.SetAlternateIconAndTextStyling(use_alternative_styling);
text_vector->push_back(result_item);
accessible_strings.push_back(
GetAccessibleStringForIcon(icon_code.value()));
} else {
// KeyboardCode does not have a corresponding IconCode. The
// key text will be styled to look like an icon ("iconified
// text").
//
// All keys including modifiers should be displayed in lower case.
const std::u16string key_string =
base::ToLowerASCII(ash::GetStringForKeyboardCode(key_code));
ash::SearchResultTextItem result_item =
CreateIconifiedTextTextItem(key_string);
result_item.SetAlternateIconAndTextStyling(use_alternative_styling);
text_vector->push_back(result_item);
accessible_strings.push_back(GetAccessibleStringForKey(key_string));
}
}
}
// Example shortcuts:
// Title: Highlight next item on shelf
// Parts:
// type: kModifier, text: alt
// type: kModifier, text: shift
// type: kKey, text: l
// type: kPlainText, text: then
// type: kKey, text: tab
// type: kPlainText, text: or
// type: kKey, text: ArrowRight
//
void KeyboardShortcutResult::PopulateTextVectorWithTextParts(
TextVector* text_vector,
std::vector<std::u16string>& accessible_strings,
const std::vector<ash::mojom::TextAcceleratorPartPtr>& accelerator_parts) {
for (auto& part : accelerator_parts) {
switch (part->type) {
case ash::mojom::TextAcceleratorPartType::kPlainText:
case ash::mojom::TextAcceleratorPartType::kDelimiter:
text_vector->push_back(CreateStringTextItem(part->text));
accessible_strings.push_back(part->text);
break;
case ash::mojom::TextAcceleratorPartType::kKey:
case ash::mojom::TextAcceleratorPartType::kModifier:
const auto icon_code = GetIconCodeByKeyString(part->text);
bool use_alternative_styling =
part->type == ash::mojom::TextAcceleratorPartType::kModifier;
if (icon_code) {
ash::SearchResultTextItem result_item =
CreateIconCodeTextItem(icon_code.value());
result_item.SetAlternateIconAndTextStyling(use_alternative_styling);
text_vector->push_back(result_item);
accessible_strings.push_back(
GetAccessibleStringForIcon(icon_code.value()));
} else {
// All keys including modifiers should be displayed in lower case.
const std::u16string key_string = base::ToLowerASCII(part->text);
ash::SearchResultTextItem result_item =
CreateIconifiedTextTextItem(key_string);
result_item.SetAlternateIconAndTextStyling(use_alternative_styling);
text_vector->push_back(result_item);
accessible_strings.push_back(GetAccessibleStringForKey(key_string));
}
break;
}
}
}
void KeyboardShortcutResult::PopulateTextVector(
TextVector* text_vector,
std::vector<std::u16string>& accessible_strings,
const ash::mojom::AcceleratorInfoPtr& accelerator_info) {
if (accelerator_info->layout_properties->is_standard_accelerator()) {
const ash::mojom::StandardAcceleratorPropertiesPtr& standard_accelerator =
accelerator_info->layout_properties->get_standard_accelerator();
PopulateTextVector(text_vector, accessible_strings,
standard_accelerator->accelerator);
} else {
const ash::mojom::TextAcceleratorPropertiesPtr& text_accelerator =
accelerator_info->layout_properties->get_text_accelerator();
PopulateTextVectorWithTextParts(text_vector, accessible_strings,
text_accelerator->parts);
}
}
void KeyboardShortcutResult::PopulateTextVectorWithTwoShortcuts(
TextVector* text_vector,
std::vector<std::u16string>& accessible_strings,
const ash::mojom::AcceleratorInfoPtr& accelerator_1,
const ash::mojom::AcceleratorInfoPtr& accelerator_2) {
CHECK(accelerator_1);
CHECK(accelerator_2);
// The default for IDS_SHORTCUT_CUSTOMIZATION_ONE_OF_TWO_CHOICES is "$1 or
// $2". In other languages, the string may be different.
const std::u16string template_string =
l10n_util::GetStringUTF16(IDS_SHORTCUT_CUSTOMIZATION_ONE_OF_TWO_CHOICES);
// Find placeholder' positions.
const size_t first_index = template_string.find_first_of(u"$");
CHECK(first_index != std::u16string::npos);
const size_t second_index = template_string.find_last_of(u"$");
CHECK(second_index != std::u16string::npos);
CHECK(second_index > first_index);
// Add text before the first placeholder if any.
if (first_index > 0) {
text_vector->push_back(
CreateStringTextItem(template_string.substr(0, first_index)));
}
// Add first shortcut.
PopulateTextVector(text_vector, accessible_strings, accelerator_1);
// Add text between the two placeholders if any.
// Since first_index points to the first char of "$1", the text we are
// interested in starts at first_index + 2.
const size_t between_len = second_index - first_index - 2;
if (between_len > 0) {
const auto between_text =
template_string.substr(first_index + 2, between_len);
text_vector->push_back(CreateStringTextItem(between_text));
accessible_strings.push_back(between_text);
}
// Add second shortcut.
PopulateTextVector(text_vector, accessible_strings, accelerator_2);
// Add text after the second placeholder if any.
if (second_index + 2 < template_string.size()) {
text_vector->push_back(
CreateStringTextItem(template_string.substr(second_index + 2)));
accessible_strings.push_back(template_string.substr(second_index + 2));
}
}
void KeyboardShortcutResult::PopulateTextVectorForNoShortcut(
TextVector* text_vector,
std::vector<std::u16string>& accessible_strings) {
std::vector<ash::mojom::TextAcceleratorPartPtr> text_parts;
text_parts.push_back(ash::mojom::TextAcceleratorPart::New(
l10n_util::GetStringUTF16(
IDS_SHORTCUT_CUSTOMIZATION_NO_SHORTCUT_ASSIGNED),
ash::mojom::TextAcceleratorPartType::kPlainText));
PopulateTextVectorWithTextParts(text_vector, accessible_strings, text_parts);
}
KeyboardShortcutResult::KeyboardShortcutResult(
Profile* profile,
const ash::shortcut_customization::mojom::SearchResultPtr& search_result)
: profile_(profile) {
accelerator_action_ =
base::NumberToString(search_result->accelerator_layout_info->action);
accelerator_category_ = base::NumberToString(
static_cast<int>(search_result->accelerator_layout_info->category));
// The ID needs to be unique among all results. The combination of action and
// its category uniquely identifies a shortcut.
set_id(base::StrCat({kKeyboardShortcutScheme, accelerator_action_, "/",
accelerator_category_}));
set_relevance(search_result->relevance_score);
SetTitle(search_result->accelerator_layout_info->description);
SetResultType(ResultType::kKeyboardShortcut);
SetMetricsType(ash::KEYBOARD_SHORTCUT);
SetDisplayType(DisplayType::kList);
SetCategory(Category::kHelp);
UpdateIcon();
// Set the details to the display name of the Keyboard Shortcuts app.
std::u16string sanitized_name = base::CollapseWhitespace(
l10n_util::GetStringUTF16(IDS_ASH_SHORTCUT_CUSTOMIZATION_APP_TITLE),
true);
base::i18n::SanitizeUserSuppliedString(&sanitized_name);
SetDetails(sanitized_name);
std::vector<std::u16string> accessible_strings;
accessible_strings.push_back(
base::StrCat({title(), u", ", details(), u", "}));
TextVector text_vector;
switch (search_result->accelerator_infos.size()) {
case 0:
// No shortcut assigned case:
PopulateTextVectorForNoShortcut(&text_vector, accessible_strings);
break;
case 1:
// Only one shortcut for the accelerator.
PopulateTextVector(&text_vector, accessible_strings,
search_result->accelerator_infos[0]);
break;
default:
// When there are more than one shortcuts, we only show the first two.
PopulateTextVectorWithTwoShortcuts(&text_vector, accessible_strings,
search_result->accelerator_infos[0],
search_result->accelerator_infos[1]);
break;
}
SetAccessibleName(base::JoinString(accessible_strings, u" "));
SetKeyboardShortcutTextVector(text_vector);
}
KeyboardShortcutResult::~KeyboardShortcutResult() = default;
void KeyboardShortcutResult::Open(int event_flags) {
// Pass the action and category of the selected shortcuts to the app so that
// the same shortcuts will be displayed in the app.
if (ash::features::IsSearchCustomizableShortcutsInLauncherEnabled()) {
chrome::ShowShortcutCustomizationApp(profile_, accelerator_action_,
accelerator_category_);
} else {
chrome::ShowShortcutCustomizationApp(profile_);
}
}
void KeyboardShortcutResult::UpdateIcon() {
ui::ImageModel icon = ui::ImageModel::FromVectorIcon(
chromeos::kKeyboardShortcutsIcon, SK_ColorTRANSPARENT, kAppIconDimension);
SetIcon(IconInfo(icon, kAppIconDimension));
}
} // namespace app_list