blob: 3756790fb284d5dde386834c3f1f7d33bc1c3b97 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/base/accelerators/global_media_keys_listener_win.h"
#include "base/bind.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/threading/thread.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/events/event.h"
#include "ui/events/keyboard_hook.h"
namespace ui {
// static
bool GlobalMediaKeysListenerWin::has_instance_ = false;
GlobalMediaKeysListenerWin::GlobalMediaKeysListenerWin(
MediaKeysListener::Delegate* delegate)
: delegate_(delegate),
thread_("KeyboardHookThread"),
main_thread_task_runner_(base::SequencedTaskRunnerHandle::Get()),
weak_factory_(this) {
DCHECK(delegate_);
DCHECK(!has_instance_);
has_instance_ = true;
}
GlobalMediaKeysListenerWin::~GlobalMediaKeysListenerWin() {
// This will reset |hook_| (if necessary) and then join on |thread_| if it's
// currently running. This guarantees that |hook_| will be empty before its
// destructor is called on this thread, so we will avoid accessing |hook_| on
// this thread.
DeleteKeyboardHookIfNecessary();
has_instance_ = false;
}
void GlobalMediaKeysListenerWin::StartWatchingMediaKey(KeyboardCode key_code) {
DCHECK(IsMediaKeycode(key_code));
{
base::AutoLock lock(key_codes_lock_);
key_codes_.insert(key_code);
}
CreateKeyboardHookIfNecessary();
}
void GlobalMediaKeysListenerWin::StopWatchingMediaKey(KeyboardCode key_code) {
DCHECK(IsMediaKeycode(key_code));
bool should_delete;
// Don't hold the lock when we delete the hook, or else we may deadlock while
// joining on |thread_| if |thread_| attempts to access |key_codes_|.
{
base::AutoLock lock(key_codes_lock_);
key_codes_.erase(key_code);
should_delete = key_codes_.empty();
}
// If we're not watching any more keys, delete the hook.
if (should_delete)
DeleteKeyboardHookIfNecessary();
}
void GlobalMediaKeysListenerWin::CreateKeyboardHookIfNecessary() {
// Create the keyboard hook if it doesn't already exist.
if (!thread_.IsRunning()) {
bool started = thread_.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_UI, 0));
LOG_IF(ERROR, !started) << "failed to start KeyboardHookThread";
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&GlobalMediaKeysListenerWin::EnsureHookOnInternalThread,
base::Unretained(this)));
}
}
void GlobalMediaKeysListenerWin::DeleteKeyboardHookIfNecessary() {
// Delete the keyboard hook if it exists.
if (thread_.IsRunning()) {
thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&GlobalMediaKeysListenerWin::DeleteHookOnInternalThread,
base::Unretained(this)));
thread_.Stop();
}
}
void GlobalMediaKeysListenerWin::EnsureHookOnInternalThread() {
DCHECK(thread_.task_runner()->RunsTasksInCurrentSequence());
DCHECK(!hook_);
hook_ = KeyboardHook::CreateMediaKeyboardHook(base::BindRepeating(
&GlobalMediaKeysListenerWin::KeyEventCallbackOnInternalThread,
base::Unretained(this)));
if (hook_) {
// Notify the delegate that we're ready to receive events for that key.
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&GlobalMediaKeysListenerWin::OnStartedWatchingMediaKeysOnMainThread,
weak_factory_.GetWeakPtr()));
} else {
LOG(ERROR) << "failed to create keyboard hook";
}
}
void GlobalMediaKeysListenerWin::DeleteHookOnInternalThread() {
DCHECK(thread_.task_runner()->RunsTasksInCurrentSequence());
hook_.reset();
}
void GlobalMediaKeysListenerWin::KeyEventCallbackOnInternalThread(
KeyEvent* event) {
DCHECK(thread_.task_runner()->RunsTasksInCurrentSequence());
{
// Ignore key events that we don't care about.
base::AutoLock lock(key_codes_lock_);
if (!key_codes_.contains(event->key_code()))
return;
}
Accelerator::KeyState state = (event->type() == ET_KEY_PRESSED)
? Accelerator::KeyState::PRESSED
: Accelerator::KeyState::RELEASED;
const ui::Accelerator accelerator(event->key_code(), 0, state);
// Send the key event to the delegate on the main thread.
main_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&GlobalMediaKeysListenerWin::KeyEventCallbackOnMainThread,
weak_factory_.GetWeakPtr(), accelerator));
// Marking the event as handled will prevent propagation in the
// KeyboardHook.
event->SetHandled();
}
void GlobalMediaKeysListenerWin::KeyEventCallbackOnMainThread(
const Accelerator& accelerator) {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
// Don't hold the lock for |delegate_|'s callback in case delegate_ calls
// Start/StopWatchingMediaKey().
{
// If we get a key event that we no longer care about, ignore it.
base::AutoLock lock(key_codes_lock_);
if (!key_codes_.contains(accelerator.key_code()))
return;
}
delegate_->OnMediaKeysAccelerator(accelerator);
}
void GlobalMediaKeysListenerWin::OnStartedWatchingMediaKeysOnMainThread() {
DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
delegate_->OnStartedWatchingMediaKeys();
}
} // namespace ui