blob: 5df2199648295d3340c2fc27512e5e1ebb61f5cb [file] [log] [blame]
// 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 "ash/accelerators/ash_accelerator_configuration.h"
#include <vector>
#include "ash/accelerators/accelerator_table.h"
#include "ash/accelerators/debug_commands.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/public/cpp/accelerators_util.h"
#include "ash/public/mojom/accelerator_configuration.mojom.h"
#include "ash/public/mojom/accelerator_info.mojom.h"
#include "base/containers/contains.h"
#include "base/containers/cxx20_erase.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ui/wm/features.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
namespace {
using AcceleratorActionMap = ui::AcceleratorMap<ash::AcceleratorAction>;
using ::ash::mojom::AcceleratorConfigResult;
void AppendAcceleratorData(
std::vector<ash::AcceleratorData>& data,
base::span<const ash::AcceleratorData> accelerators) {
data.reserve(data.size() + accelerators.size());
for (const auto& accelerator : accelerators) {
data.push_back(accelerator);
}
}
void SetLookupMaps(base::span<const ash::AcceleratorData> accelerators,
ash::ActionIdToAcceleratorsMap& id_to_accelerator,
AcceleratorActionMap& accelerator_to_id) {
for (const auto& acceleratorData : accelerators) {
ui::Accelerator accelerator(acceleratorData.keycode,
acceleratorData.modifiers);
accelerator.set_key_state(acceleratorData.trigger_on_press
? ui::Accelerator::KeyState::PRESSED
: ui::Accelerator::KeyState::RELEASED);
accelerator_to_id.InsertNew(
std::make_pair(accelerator, acceleratorData.action));
id_to_accelerator[static_cast<uint32_t>(acceleratorData.action)].push_back(
accelerator);
}
}
std::vector<ash::AcceleratorData> GetDefaultAccelerators() {
std::vector<ash::AcceleratorData> accelerators;
AppendAcceleratorData(
accelerators,
base::make_span(ash::kAcceleratorData, ash::kAcceleratorDataLength));
if (::features::IsImprovedKeyboardShortcutsEnabled()) {
AppendAcceleratorData(
accelerators,
base::make_span(ash::kEnableWithPositionalAcceleratorsData,
ash::kEnableWithPositionalAcceleratorsDataLength));
AppendAcceleratorData(
accelerators,
base::make_span(
ash::kEnabledWithImprovedDesksKeyboardShortcutsAcceleratorData,
ash::
kEnabledWithImprovedDesksKeyboardShortcutsAcceleratorDataLength));
} else if (::features::IsNewShortcutMappingEnabled()) {
AppendAcceleratorData(
accelerators,
base::make_span(ash::kEnableWithNewMappingAcceleratorData,
ash::kEnableWithNewMappingAcceleratorDataLength));
} else {
AppendAcceleratorData(
accelerators,
base::make_span(ash::kDisableWithNewMappingAcceleratorData,
ash::kDisableWithNewMappingAcceleratorDataLength));
}
if (ash::features::IsSameAppWindowCycleEnabled()) {
AppendAcceleratorData(
accelerators,
base::make_span(
ash::kEnableWithSameAppWindowCycleAcceleratorData,
ash::kEnableWithSameAppWindowCycleAcceleratorDataLength));
}
if (chromeos::wm::features::IsWindowLayoutMenuEnabled()) {
AppendAcceleratorData(
accelerators,
base::make_span(ash::kEnableWithFloatWindowAcceleratorData,
ash::kEnableWithFloatWindowAcceleratorDataLength));
}
if (ash::features::IsGameDashboardEnabled()) {
AppendAcceleratorData(
accelerators,
base::make_span(ash::kToggleGameDashboardAcceleratorData,
ash::kToggleGameDashboardAcceleratorDataLength));
}
// Debug accelerators.
if (ash::debug::DebugAcceleratorsEnabled()) {
AppendAcceleratorData(accelerators,
base::make_span(ash::kDebugAcceleratorData,
ash::kDebugAcceleratorDataLength));
}
// Developer accelerators.
if (ash::debug::DeveloperAcceleratorsEnabled()) {
AppendAcceleratorData(
accelerators, base::make_span(ash::kDeveloperAcceleratorData,
ash::kDeveloperAcceleratorDataLength));
}
return accelerators;
}
} // namespace
namespace ash {
AshAcceleratorConfiguration::AshAcceleratorConfiguration()
: AcceleratorConfiguration(ash::mojom::AcceleratorSource::kAsh) {}
AshAcceleratorConfiguration::~AshAcceleratorConfiguration() = default;
// TODO(jimmyxgong): Implement all functions below as these are only stubs.
const std::vector<ui::Accelerator>&
AshAcceleratorConfiguration::GetAcceleratorsForAction(
AcceleratorActionId action_id) {
const auto accelerator_iter = id_to_accelerators_.find(action_id);
DCHECK(accelerator_iter != id_to_accelerators_.end());
return accelerator_iter->second;
}
bool AshAcceleratorConfiguration::IsMutable() const {
// TODO(longbowei): Implement this function as this is only stub for now.
return false;
}
bool AshAcceleratorConfiguration::IsDeprecated(
const ui::Accelerator& accelerator) const {
return deprecated_accelerators_to_id_.Find(accelerator);
}
const AcceleratorAction* AshAcceleratorConfiguration::FindAcceleratorAction(
const ui::Accelerator& accelerator) const {
// If the accelerator is deprecated, return the action ID first.
const AcceleratorAction* deprecated_action_id =
deprecated_accelerators_to_id_.Find(accelerator);
if (deprecated_action_id) {
return deprecated_action_id;
}
return accelerator_to_id_.Find(accelerator);
}
AcceleratorConfigResult AshAcceleratorConfiguration::AddUserAccelerator(
AcceleratorActionId action_id,
const ui::Accelerator& accelerator) {
return AcceleratorConfigResult::kActionLocked;
}
AcceleratorConfigResult AshAcceleratorConfiguration::RemoveAccelerator(
AcceleratorActionId action_id,
const ui::Accelerator& accelerator) {
DCHECK(::features::IsShortcutCustomizationEnabled());
AcceleratorConfigResult result = DoRemoveAccelerator(action_id, accelerator);
if (result == AcceleratorConfigResult::kSuccess) {
UpdateAndNotifyAccelerators();
}
VLOG(1) << "RemovedAccelerator called for ActionID: " << action_id
<< ", Accelerator: " << accelerator.GetShortcutText()
<< " returned: " << static_cast<int>(result);
return result;
}
AcceleratorConfigResult AshAcceleratorConfiguration::ReplaceAccelerator(
AcceleratorActionId action_id,
const ui::Accelerator& old_acc,
const ui::Accelerator& new_acc) {
return AcceleratorConfigResult::kActionLocked;
}
AcceleratorConfigResult AshAcceleratorConfiguration::RestoreDefault(
AcceleratorActionId action_id) {
const auto& current_accelerators = id_to_accelerators_.find(action_id);
if (current_accelerators == id_to_accelerators_.end()) {
VLOG(1) << "ResetAction called for ActionID: " << action_id
<< " returned with error: " << AcceleratorConfigResult::kNotFound;
return AcceleratorConfigResult::kNotFound;
}
auto& accelerators_for_id = current_accelerators->second;
// Clear reverse mapping first.
for (const auto& accelerator : accelerators_for_id) {
// There should never be a mismatch between the two maps, `Get()` does an
// implicit DCHECK too.
auto& found_id = accelerator_to_id_.Get(accelerator);
if (found_id != action_id) {
VLOG(1) << "ResetAction called for ActionID: " << action_id
<< " returned with error: " << AcceleratorConfigResult::kNotFound;
return AcceleratorConfigResult::kNotFound;
}
accelerator_to_id_.Erase(accelerator);
}
// Clear lookup map.
accelerators_for_id.clear();
// Restore the system default accelerator(s) for this action only if it the
// default is not used by another accelerator.
// Users will have to manually re-add the default accelerator if there exists
// a conflict.
const auto& defaults = default_id_to_accelerators_cache_.find(action_id);
DCHECK(defaults != default_id_to_accelerators_cache_.end());
// Iterate through the default and only add back the default if they're not
// in use.
for (const auto& default_accelerator : defaults->second) {
if (!accelerator_to_id_.Find(default_accelerator)) {
accelerators_for_id.push_back(default_accelerator);
accelerator_to_id_.InsertNew(
{default_accelerator, static_cast<AcceleratorAction>(action_id)});
}
}
// TODO(jimmyxgong): Update prefs when available.
UpdateAndNotifyAccelerators();
VLOG(1) << "ResetAction called for ActionID: " << action_id
<< " returned successfully.";
return AcceleratorConfigResult::kSuccess;
}
AcceleratorConfigResult AshAcceleratorConfiguration::RestoreAllDefaults() {
accelerators_.clear();
id_to_accelerators_.clear();
accelerator_to_id_.Clear();
deprecated_accelerators_to_id_.Clear();
actions_with_deprecations_.clear();
// TODO(jimmyxgong): Reset the prefs here too.
id_to_accelerators_ = default_id_to_accelerators_cache_;
accelerator_to_id_ = default_accelerators_to_id_cache_;
deprecated_accelerators_to_id_ = default_deprecated_accelerators_to_id_cache_;
actions_with_deprecations_ = default_actions_with_deprecations_cache_;
UpdateAndNotifyAccelerators();
return AcceleratorConfigResult::kSuccess;
}
void AshAcceleratorConfiguration::Initialize() {
Initialize(GetDefaultAccelerators());
InitializeDeprecatedAccelerators();
}
void AshAcceleratorConfiguration::Initialize(
base::span<const AcceleratorData> accelerators) {
accelerators_.clear();
deprecated_accelerators_to_id_.Clear();
id_to_accelerators_.clear();
accelerator_to_id_.Clear();
default_accelerators_to_id_cache_.Clear();
default_id_to_accelerators_cache_.clear();
// Cache these accelerators as the default.
SetLookupMaps(accelerators, default_id_to_accelerators_cache_,
default_accelerators_to_id_cache_);
// TODO(jimmyxgong): Before adding the accelerators to the mappings, apply
// pref remaps.
AddAccelerators(accelerators);
}
void AshAcceleratorConfiguration::InitializeDeprecatedAccelerators() {
base::span<const DeprecatedAcceleratorData> deprecated_accelerator_data(
kDeprecatedAcceleratorsData, kDeprecatedAcceleratorsDataLength);
base::span<const AcceleratorData> deprecated_accelerators(
kDeprecatedAccelerators, kDeprecatedAcceleratorsLength);
InitializeDeprecatedAccelerators(std::move(deprecated_accelerator_data),
std::move(deprecated_accelerators));
}
void AshAcceleratorConfiguration::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void AshAcceleratorConfiguration::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
// This function must only be called after Initialize().
void AshAcceleratorConfiguration::InitializeDeprecatedAccelerators(
base::span<const DeprecatedAcceleratorData> deprecated_data,
base::span<const AcceleratorData> deprecated_accelerators) {
for (const auto& data : deprecated_data) {
actions_with_deprecations_[data.action] = &data;
}
for (const auto& data : deprecated_accelerators) {
deprecated_accelerators_to_id_.InsertNew(
{{data.keycode, data.modifiers},
static_cast<AcceleratorAction>(data.action)});
}
// Cache a copy of the default deprecated accelerators.
default_actions_with_deprecations_cache_ = actions_with_deprecations_;
default_deprecated_accelerators_to_id_cache_ = deprecated_accelerators_to_id_;
UpdateAndNotifyAccelerators();
}
void AshAcceleratorConfiguration::AddAccelerators(
base::span<const AcceleratorData> accelerators) {
SetLookupMaps(accelerators, id_to_accelerators_, accelerator_to_id_);
UpdateAndNotifyAccelerators();
}
AcceleratorConfigResult AshAcceleratorConfiguration::DoRemoveAccelerator(
AcceleratorActionId action_id,
const ui::Accelerator& accelerator) {
DCHECK(::features::IsShortcutCustomizationEnabled());
// If the accelerator is deprecated, remove it.
const AcceleratorAction* deprecated_action_id =
deprecated_accelerators_to_id_.Find(accelerator);
if (deprecated_action_id && *deprecated_action_id == action_id) {
deprecated_accelerators_to_id_.Erase(accelerator);
actions_with_deprecations_.erase(action_id);
return AcceleratorConfigResult::kSuccess;
}
const AcceleratorAction* found_id = accelerator_to_id_.Find(accelerator);
auto found_accelerators_iter = id_to_accelerators_.find(action_id);
if (found_accelerators_iter == id_to_accelerators_.end() || !found_id) {
return AcceleratorConfigResult::kNotFound;
}
DCHECK(*found_id == action_id);
// Remove accelerator from lookup map.
base::Erase(found_accelerators_iter->second, accelerator);
// Remove accelerator from reverse lookup map.
accelerator_to_id_.Erase(accelerator);
return AcceleratorConfigResult::kSuccess;
}
const DeprecatedAcceleratorData*
AshAcceleratorConfiguration::GetDeprecatedAcceleratorData(
AcceleratorActionId action) {
auto it = actions_with_deprecations_.find(action);
if (it == actions_with_deprecations_.end()) {
return nullptr;
}
return it->second;
}
void AshAcceleratorConfiguration::NotifyAcceleratorsUpdated() {
if (!::features::IsShortcutCustomizationEnabled()) {
return;
}
for (auto& observer : observer_list_) {
observer.OnAcceleratorsUpdated();
}
}
absl::optional<AcceleratorAction>
AshAcceleratorConfiguration::GetIdForDefaultAccelerator(
ui::Accelerator accelerator) {
AcceleratorAction* found_id =
default_accelerators_to_id_cache_.Find(accelerator);
return found_id ? absl::optional<AcceleratorAction>(*found_id)
: absl::nullopt;
}
std::vector<ui::Accelerator>
AshAcceleratorConfiguration::GetDefaultAcceleratorsForId(
AcceleratorActionId id) {
const auto iter = default_id_to_accelerators_cache_.find(id);
DCHECK(iter != default_id_to_accelerators_cache_.end());
return iter->second;
}
bool AshAcceleratorConfiguration::IsValid(uint32_t id) const {
return id_to_accelerators_.contains(id) &&
default_id_to_accelerators_cache_.contains(id);
}
void AshAcceleratorConfiguration::UpdateAndNotifyAccelerators() {
// Re-populate `accelerators_` which contains all currently available
// accelerators and deprecated accelerators, if present.
accelerators_.clear();
accelerators_.reserve(accelerator_to_id_.size() +
deprecated_accelerators_to_id_.size());
for (const auto& [accel, action_id] : accelerator_to_id_) {
accelerators_.push_back(accel);
}
for (const auto& [accel, action_id] : deprecated_accelerators_to_id_) {
accelerators_.push_back(accel);
}
UpdateAccelerators(id_to_accelerators_);
NotifyAcceleratorsUpdated();
}
} // namespace ash