blob: e1c49f5cd9bd249398a14905a6fb41d54238e1ec [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/extensions/global_shortcut_listener_x11.h"
#include <stddef.h>
#include "base/stl_util.h"
#include "content/public/browser/browser_thread.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/x/x11_util.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/x/xproto.h"
using content::BrowserThread;
namespace {
// The modifiers masks used for grabing keys. Due to XGrabKey only working on
// exact modifiers, we need to grab all key combination including zero or more
// of the following: Num lock, Caps lock and Scroll lock. So that we can make
// sure the behavior of global shortcuts is consistent on all platforms.
const x11::ModMask kModifiersMasks[] = {
{}, // No additional modifier.
x11::ModMask::c_2, // Num lock
x11::ModMask::Lock, // Caps lock
x11::ModMask::c_5, // Scroll lock
x11::ModMask::c_2 | x11::ModMask::Lock,
x11::ModMask::c_2 | x11::ModMask::c_5,
x11::ModMask::Lock | x11::ModMask::c_5,
x11::ModMask::c_2 | x11::ModMask::Lock | x11::ModMask::c_5};
x11::ModMask GetNativeModifiers(const ui::Accelerator& accelerator) {
constexpr auto kNoMods = x11::ModMask{};
return (accelerator.IsShiftDown() ? x11::ModMask::Shift : kNoMods) |
(accelerator.IsCtrlDown() ? x11::ModMask::Control : kNoMods) |
(accelerator.IsAltDown() ? x11::ModMask::c_1 : kNoMods);
}
} // namespace
namespace extensions {
// static
GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
static GlobalShortcutListenerX11* instance = new GlobalShortcutListenerX11();
return instance;
}
GlobalShortcutListenerX11::GlobalShortcutListenerX11()
: is_listening_(false),
connection_(x11::Connection::Get()),
x_root_window_(ui::GetX11RootWindow()) {
CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
}
GlobalShortcutListenerX11::~GlobalShortcutListenerX11() {
if (is_listening_)
StopListening();
}
void GlobalShortcutListenerX11::StartListening() {
DCHECK(!is_listening_); // Don't start twice.
DCHECK(!registered_hot_keys_.empty()); // Also don't start if no hotkey is
// registered.
ui::PlatformEventSource::GetInstance()->AddPlatformEventDispatcher(this);
is_listening_ = true;
}
void GlobalShortcutListenerX11::StopListening() {
DCHECK(is_listening_); // No point if we are not already listening.
DCHECK(registered_hot_keys_.empty()); // Make sure the set is clean before
// ending.
ui::PlatformEventSource::GetInstance()->RemovePlatformEventDispatcher(this);
is_listening_ = false;
}
bool GlobalShortcutListenerX11::CanDispatchEvent(
const ui::PlatformEvent& event) {
return event->type() == ui::ET_KEY_PRESSED;
}
uint32_t GlobalShortcutListenerX11::DispatchEvent(
const ui::PlatformEvent& event) {
CHECK_EQ(event->type(), ui::ET_KEY_PRESSED);
OnKeyPressEvent(*event->AsKeyEvent());
return ui::POST_DISPATCH_NONE;
}
bool GlobalShortcutListenerX11::RegisterAcceleratorImpl(
const ui::Accelerator& accelerator) {
DCHECK(registered_hot_keys_.find(accelerator) == registered_hot_keys_.end());
auto modifiers = GetNativeModifiers(accelerator);
auto keysym = XKeysymForWindowsKeyCode(accelerator.key_code(), false);
auto keycode = connection_->KeysymToKeycode(keysym);
// Because XGrabKey only works on the exact modifiers mask, we should register
// our hot keys with modifiers that we want to ignore, including Num lock,
// Caps lock, Scroll lock. See comment about |kModifiersMasks|.
x11::Future<void> grab_requests[base::size(kModifiersMasks)];
for (size_t i = 0; i < base::size(kModifiersMasks); i++) {
grab_requests[i] = connection_->GrabKey(
{false, x_root_window_, modifiers | kModifiersMasks[i], keycode,
x11::GrabMode::Async, x11::GrabMode::Async});
}
connection_->Flush();
for (auto& grab_request : grab_requests) {
if (grab_request.Sync().error) {
// We may have part of the hotkeys registered, clean up.
for (auto mask : kModifiersMasks)
connection_->UngrabKey({keycode, x_root_window_, modifiers | mask});
return false;
}
}
registered_hot_keys_.insert(accelerator);
return true;
}
void GlobalShortcutListenerX11::UnregisterAcceleratorImpl(
const ui::Accelerator& accelerator) {
DCHECK(registered_hot_keys_.find(accelerator) != registered_hot_keys_.end());
auto modifiers = GetNativeModifiers(accelerator);
auto keysym = XKeysymForWindowsKeyCode(accelerator.key_code(), false);
auto keycode = connection_->KeysymToKeycode(keysym);
for (auto mask : kModifiersMasks)
connection_->UngrabKey({keycode, x_root_window_, modifiers | mask});
registered_hot_keys_.erase(accelerator);
}
void GlobalShortcutListenerX11::OnKeyPressEvent(const ui::KeyEvent& event) {
DCHECK_EQ(event.type(), ui::ET_KEY_PRESSED);
ui::Accelerator accelerator(event.key_code(), event.flags());
if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end())
NotifyKeyPressed(accelerator);
}
} // namespace extensions