// Copyright 2013 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/extensions/global_shortcut_listener_win.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/not_fatal_until.h"
#include "base/win/win_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/media_keys_listener_manager.h"
#include "extensions/common/command.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
#include "ui/gfx/win/singleton_hwnd.h"
#include "ui/gfx/win/singleton_hwnd_hot_key_observer.h"
using content::BrowserThread;
namespace extensions {
// static
GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
static GlobalShortcutListenerWin* instance =
new GlobalShortcutListenerWin();
return instance;
}
GlobalShortcutListenerWin::GlobalShortcutListenerWin()
: is_listening_(false) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
GlobalShortcutListenerWin::~GlobalShortcutListenerWin() {
if (is_listening_) {
StopListening();
}
}
void GlobalShortcutListenerWin::StartListening() {
DCHECK(!is_listening_); // Don't start twice.
DCHECK(!hotkeys_.empty()); // Also don't start if no hotkey is registered.
is_listening_ = true;
}
void GlobalShortcutListenerWin::StopListening() {
DCHECK(is_listening_); // No point if we are not already listening.
DCHECK(hotkeys_.empty()); // Make sure the map is clean before ending.
is_listening_ = false;
}
void GlobalShortcutListenerWin::OnWndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
// SingletonHwndHotKeyObservers should only send us hot key messages.
DCHECK_EQ(WM_HOTKEY, static_cast<int>(message));
int key_code = HIWORD(lparam);
int modifiers = 0;
modifiers |= (LOWORD(lparam) & MOD_SHIFT) ? ui::EF_SHIFT_DOWN : 0;
modifiers |= (LOWORD(lparam) & MOD_ALT) ? ui::EF_ALT_DOWN : 0;
modifiers |= (LOWORD(lparam) & MOD_CONTROL) ? ui::EF_CONTROL_DOWN : 0;
ui::Accelerator accelerator(
ui::KeyboardCodeForWindowsKeyCode(key_code), modifiers);
NotifyKeyPressed(accelerator);
}
bool GlobalShortcutListenerWin::RegisterAcceleratorImpl(
const ui::Accelerator& accelerator) {
DCHECK(hotkeys_.find(accelerator) == hotkeys_.end());
// TODO(crbug.com/40622191): We should be using
// |media_keys_listener_manager->StartWatchingMediaKey(...)| here, but that
// currently breaks the GlobalCommandsApiTest.GlobalDuplicatedMediaKey test.
// Instead, we'll just disable the MediaKeysListenerManager handling here, and
// listen using the fallback RegisterHotKey method.
if (content::MediaKeysListenerManager::IsMediaKeysListenerManagerEnabled() &&
Command::IsMediaKey(accelerator)) {
content::MediaKeysListenerManager* media_keys_listener_manager =
content::MediaKeysListenerManager::GetInstance();
DCHECK(media_keys_listener_manager);
registered_media_keys_++;
media_keys_listener_manager->DisableInternalMediaKeyHandling();
}
// Convert Accelerator modifiers to OS modifiers.
int modifiers = 0;
modifiers |= accelerator.IsShiftDown() ? MOD_SHIFT : 0;
modifiers |= accelerator.IsCtrlDown() ? MOD_CONTROL : 0;
modifiers |= accelerator.IsAltDown() ? MOD_ALT : 0;
// Create an observer that registers a hot key for |accelerator|.
std::unique_ptr<gfx::SingletonHwndHotKeyObserver> observer =
gfx::SingletonHwndHotKeyObserver::Create(
base::BindRepeating(&GlobalShortcutListenerWin::OnWndProc,
base::Unretained(this)),
accelerator.key_code(), modifiers);
if (!observer) {
// Most likely error: 1409 (Hotkey already registered).
return false;
}
hotkeys_[accelerator] = std::move(observer);
return true;
}
void GlobalShortcutListenerWin::UnregisterAcceleratorImpl(
const ui::Accelerator& accelerator) {
HotKeyMap::iterator it = hotkeys_.find(accelerator);
CHECK(it != hotkeys_.end(), base::NotFatalUntil::M130);
// TODO(crbug.com/40622191): We should be using
// |media_keys_listener_manager->StopWatchingMediaKey(...)| here.
if (content::MediaKeysListenerManager::IsMediaKeysListenerManagerEnabled() &&
Command::IsMediaKey(accelerator)) {
registered_media_keys_--;
DCHECK_GE(registered_media_keys_, 0);
if (registered_media_keys_ == 0) {
content::MediaKeysListenerManager* media_keys_listener_manager =
content::MediaKeysListenerManager::GetInstance();
DCHECK(media_keys_listener_manager);
media_keys_listener_manager->EnableInternalMediaKeyHandling();
}
}
hotkeys_.erase(it);
}
void GlobalShortcutListenerWin::OnMediaKeysAccelerator(
const ui::Accelerator& accelerator) {
// We should not receive media key events that we didn't register for.
DCHECK(hotkeys_.find(accelerator) != hotkeys_.end());
NotifyKeyPressed(accelerator);
}
} // namespace extensions