1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/extensions/global_shortcut_listener_x11.h"
6
7#include "content/public/browser/browser_thread.h"
8#include "ui/base/accelerators/accelerator.h"
9#include "ui/events/keycodes/keyboard_code_conversion_x.h"
10#include "ui/events/platform/x11/x11_event_source.h"
11#include "ui/gfx/x/x11_error_tracker.h"
12#include "ui/gfx/x/x11_types.h"
13
14using content::BrowserThread;
15
16namespace {
17
18// The modifiers masks used for grabing keys. Due to XGrabKey only working on
19// exact modifiers, we need to grab all key combination including zero or more
20// of the following: Num lock, Caps lock and Scroll lock. So that we can make
21// sure the behavior of global shortcuts is consistent on all platforms.
22const unsigned int kModifiersMasks[] = {
23  0,                                // No additional modifier.
24  Mod2Mask,                         // Num lock
25  LockMask,                         // Caps lock
26  Mod5Mask,                         // Scroll lock
27  Mod2Mask | LockMask,
28  Mod2Mask | Mod5Mask,
29  LockMask | Mod5Mask,
30  Mod2Mask | LockMask | Mod5Mask
31};
32
33int GetNativeModifiers(const ui::Accelerator& accelerator) {
34  int modifiers = 0;
35  modifiers |= accelerator.IsShiftDown() ? ShiftMask : 0;
36  modifiers |= accelerator.IsCtrlDown() ? ControlMask : 0;
37  modifiers |= accelerator.IsAltDown() ? Mod1Mask : 0;
38
39  return modifiers;
40}
41
42}  // namespace
43
44namespace extensions {
45
46// static
47GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
48  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
49  static GlobalShortcutListenerX11* instance =
50      new GlobalShortcutListenerX11();
51  return instance;
52}
53
54GlobalShortcutListenerX11::GlobalShortcutListenerX11()
55    : is_listening_(false),
56      x_display_(gfx::GetXDisplay()),
57      x_root_window_(DefaultRootWindow(x_display_)) {
58  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
59}
60
61GlobalShortcutListenerX11::~GlobalShortcutListenerX11() {
62  if (is_listening_)
63    StopListening();
64}
65
66void GlobalShortcutListenerX11::StartListening() {
67  DCHECK(!is_listening_);  // Don't start twice.
68  DCHECK(!registered_hot_keys_.empty());  // Also don't start if no hotkey is
69                                          // registered.
70
71  ui::X11EventSource::GetInstance()->AddPlatformEventDispatcher(this);
72
73  is_listening_ = true;
74}
75
76void GlobalShortcutListenerX11::StopListening() {
77  DCHECK(is_listening_);  // No point if we are not already listening.
78  DCHECK(registered_hot_keys_.empty());  // Make sure the set is clean before
79                                         // ending.
80
81  ui::X11EventSource::GetInstance()->RemovePlatformEventDispatcher(this);
82
83  is_listening_ = false;
84}
85
86bool GlobalShortcutListenerX11::CanDispatchEvent(
87    const ui::PlatformEvent& event) {
88  return event->type == KeyPress;
89}
90
91uint32_t GlobalShortcutListenerX11::DispatchEvent(
92    const ui::PlatformEvent& event) {
93  CHECK_EQ(KeyPress, event->type);
94  OnXKeyPressEvent(event);
95
96  return ui::POST_DISPATCH_NONE;
97}
98
99bool GlobalShortcutListenerX11::RegisterAcceleratorImpl(
100    const ui::Accelerator& accelerator) {
101  DCHECK(registered_hot_keys_.find(accelerator) == registered_hot_keys_.end());
102
103  int modifiers = GetNativeModifiers(accelerator);
104  KeyCode keycode = XKeysymToKeycode(x_display_,
105      XKeysymForWindowsKeyCode(accelerator.key_code(), false));
106  gfx::X11ErrorTracker err_tracker;
107
108  // Because XGrabKey only works on the exact modifiers mask, we should register
109  // our hot keys with modifiers that we want to ignore, including Num lock,
110  // Caps lock, Scroll lock. See comment about |kModifiersMasks|.
111  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
112    XGrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
113             x_root_window_, False, GrabModeAsync, GrabModeAsync);
114  }
115
116  if (err_tracker.FoundNewError()) {
117    // We may have part of the hotkeys registered, clean up.
118    for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
119      XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
120                 x_root_window_);
121    }
122
123    return false;
124  }
125
126  registered_hot_keys_.insert(accelerator);
127  return true;
128}
129
130void GlobalShortcutListenerX11::UnregisterAcceleratorImpl(
131    const ui::Accelerator& accelerator) {
132  DCHECK(registered_hot_keys_.find(accelerator) != registered_hot_keys_.end());
133
134  int modifiers = GetNativeModifiers(accelerator);
135  KeyCode keycode = XKeysymToKeycode(x_display_,
136      XKeysymForWindowsKeyCode(accelerator.key_code(), false));
137
138  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
139    XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
140               x_root_window_);
141  }
142  registered_hot_keys_.erase(accelerator);
143}
144
145void GlobalShortcutListenerX11::OnXKeyPressEvent(::XEvent* x_event) {
146  DCHECK(x_event->type == KeyPress);
147  int modifiers = 0;
148  modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0;
149  modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0;
150  modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0;
151
152  ui::Accelerator accelerator(
153      ui::KeyboardCodeFromXKeyEvent(x_event), modifiers);
154  if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end())
155    NotifyKeyPressed(accelerator);
156}
157
158}  // namespace extensions
159