blob: 547c575d9546596c0943f8267f648f8e627a8f52 [file] [log] [blame]
// Copyright 2025 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/glic/widget/local_hotkey_manager.h"
#include <algorithm>
#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/glic/glic_pref_names.h"
#include "chrome/browser/glic/widget/glic_window_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "ui/base/accelerators/accelerator_manager.h"
#include "ui/base/accelerators/command.h"
namespace glic {
namespace {
#if BUILDFLAG(IS_MAC)
constexpr int kFocusToggleAcceleratorModifiers =
ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN;
#else
constexpr int kFocusToggleAcceleratorModifiers =
ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN;
#endif
constexpr auto kHotkeyToPrefMap =
base::MakeFixedFlatMap<LocalHotkeyManager::Hotkey, const char*>(
{{LocalHotkeyManager::Hotkey::kFocusToggle,
prefs::kGlicFocusToggleHotkey}});
constexpr std::array kCloseAccelerators = {
ui::Accelerator{ui::VKEY_ESCAPE, ui::EF_NONE},
#if BUILDFLAG(IS_MAC)
ui::Accelerator{ui::VKEY_W, ui::EF_COMMAND_DOWN}
#else
ui::Accelerator{ui::VKEY_W, ui::EF_CONTROL_DOWN}
#endif
};
#if BUILDFLAG(IS_WIN)
constexpr std::array kTitleBarContextMenuAccelerators = {
ui::Accelerator{ui::VKEY_SPACE, ui::EF_ALT_DOWN}};
#endif
constexpr auto kHotkeyToStaticAcceleratorsMap =
base::MakeFixedFlatMap<LocalHotkeyManager::Hotkey,
base::span<const ui::Accelerator>>(
{{LocalHotkeyManager::Hotkey::kClose, kCloseAccelerators},
#if BUILDFLAG(IS_WIN)
{LocalHotkeyManager::Hotkey::kTitleBarContextMenu,
kTitleBarContextMenuAccelerators}
#endif
});
// Compile-time helper to check if the keys in two maps are disjoint.
// It checks if any key from map1 exists in map2.
template <typename Map1, typename Map2>
constexpr bool AreKeysDisjoint(const Map1& map1, const Map2& map2) {
for (const auto& entry_map1 : map1) {
const auto& key_to_find = entry_map1.first;
auto it = std::lower_bound(map2.begin(), map2.end(), key_to_find,
[](const auto& element, const auto& key) {
return element.first < key;
});
if (it != map2.end() && it->first == key_to_find) {
return false; // Key from map1 found in map2, so not disjoint.
}
}
return true;
}
// Compile-time check to ensure that hotkeys defined as static (in
// kHotkeyToStaticAcceleratorsMap) are not also defined as configurable (in
// kHotkeyToPrefMap).
static_assert(
AreKeysDisjoint(kHotkeyToStaticAcceleratorsMap, kHotkeyToPrefMap),
"A hotkey cannot be defined in both kHotkeyToStaticAcceleratorsMap and "
"kHotkeyToPrefMap. Check definitions.");
} // namespace
LocalHotkeyManager::LocalHotkeyManager(
base::WeakPtr<GlicWindowController> window_controller,
std::unique_ptr<Delegate> delegate)
: window_controller_(window_controller), delegate_(std::move(delegate)) {
CHECK(delegate_);
CHECK(g_browser_process);
pref_registrar_.Init(g_browser_process->local_state());
for (Hotkey hotkey : delegate_->GetSupportedHotkeys()) {
auto pref_name_iter = kHotkeyToPrefMap.find(hotkey);
if (pref_name_iter == kHotkeyToPrefMap.end()) {
continue;
}
pref_registrar_.Add(pref_name_iter->second,
base::BindRepeating(&LocalHotkeyManager::RegisterHotkey,
base::Unretained(this), hotkey));
}
}
LocalHotkeyManager::~LocalHotkeyManager() {
// Ensure that registrations are deleted before the WeakPtrs are invalidated
// as they might be depending on it.
hotkey_registrations_.clear();
}
// static
ui::Accelerator LocalHotkeyManager::GetDefaultAccelerator(Hotkey hotkey) {
CHECK(kHotkeyToPrefMap.contains(hotkey));
switch (hotkey) {
case Hotkey::kFocusToggle:
return ui::Accelerator{ui::VKEY_G, kFocusToggleAcceleratorModifiers};
default:
NOTREACHED();
}
}
// static
base::span<const ui::Accelerator> LocalHotkeyManager::GetStaticAccelerators(
Hotkey hotkey) {
auto iter = kHotkeyToStaticAcceleratorsMap.find(hotkey);
CHECK(iter != kHotkeyToStaticAcceleratorsMap.end());
return iter->second;
}
// static
ui::Accelerator LocalHotkeyManager::GetConfigurableAccelerator(Hotkey hotkey) {
auto pref_name_iter = kHotkeyToPrefMap.find(hotkey);
CHECK(pref_name_iter != kHotkeyToPrefMap.end());
const ui::Accelerator accelerator = ui::Command::StringToAccelerator(
g_browser_process->local_state()->GetString(pref_name_iter->second));
// Return empty accelerator if an invalid modifier was set.
if (!accelerator.IsEmpty() &&
ui::Accelerator::MaskOutKeyEventFlags(accelerator.modifiers()) == 0) {
return ui::Accelerator();
}
return accelerator;
}
LocalHotkeyManager::Hotkey LocalHotkeyManager::GetHotkeyEnum(
ui::Accelerator accelerator) {
CHECK(delegate_);
for (Hotkey hotkey : delegate_->GetSupportedHotkeys()) {
if (auto iter = kHotkeyToStaticAcceleratorsMap.find(hotkey);
iter != kHotkeyToStaticAcceleratorsMap.end()) {
for (const ui::Accelerator& static_accelerator : iter->second) {
if (static_accelerator == accelerator) {
return hotkey;
}
}
} else if (GetConfigurableAccelerator(hotkey) == accelerator) {
return hotkey;
}
}
NOTREACHED() << accelerator.GetShortcutText() << " isn't a supported hotkey";
}
void LocalHotkeyManager::InitializeAccelerators() {
CHECK(delegate_);
for (Hotkey hotkey : delegate_->GetSupportedHotkeys()) {
RegisterHotkey(hotkey);
}
}
bool LocalHotkeyManager::AcceleratorPressed(
const ui::Accelerator& accelerator) {
if (!window_controller_ || !delegate_) {
return false;
}
auto hotkey = GetHotkeyEnum(accelerator);
return delegate_->AcceleratorPressed(hotkey);
}
bool LocalHotkeyManager::CanHandleAccelerators() const {
if (!window_controller_) {
return false;
}
return window_controller_->IsShowing();
}
std::vector<ui::Accelerator> LocalHotkeyManager::GetAccelerators(
Hotkey hotkey) {
std::vector<ui::Accelerator> accelerators_to_register;
if (kHotkeyToPrefMap.contains(hotkey)) {
// Configurable hotkey
ui::Accelerator accelerator = GetConfigurableAccelerator(hotkey);
if (!accelerator.IsEmpty()) {
accelerators_to_register.push_back(accelerator);
}
} else {
// Static hotkey. GetStaticAccelerators will CHECK that it's in
// kHotkeyToStaticAcceleratorsMap and not in kHotkeyToPrefMap.
base::span<const ui::Accelerator> static_accelerators =
GetStaticAccelerators(hotkey);
accelerators_to_register.assign(static_accelerators.begin(),
static_accelerators.end());
}
return accelerators_to_register;
}
void LocalHotkeyManager::RegisterHotkey(Hotkey hotkey) {
// Clear previous registrations for this hotkey.
// The unique_ptrs in the vector will be destroyed, unregistering them.
hotkey_registrations_.erase(hotkey);
if (!delegate_) {
return;
}
std::vector<std::unique_ptr<ScopedHotkeyRegistration>> new_registrations;
for (const ui::Accelerator& accelerator : GetAccelerators(hotkey)) {
if (auto registration = delegate_->CreateScopedHotkeyRegistration(
accelerator, GetWeakPtr())) {
new_registrations.push_back(std::move(registration));
}
}
if (!new_registrations.empty()) {
hotkey_registrations_.emplace(hotkey, std::move(new_registrations));
}
}
} // namespace glic