global_shortcut_listener_x11.cc revision f2477e01787aa58f445919b809d89e252beef54f
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 "base/x11/x11_error_tracker.h"
8#include "content/public/browser/browser_thread.h"
9#include "ui/base/accelerators/accelerator.h"
10#include "ui/events/keycodes/keyboard_code_conversion_x.h"
11#include "ui/gfx/x/x11_types.h"
12
13#if defined(TOOLKIT_GTK)
14#include <gdk/gdkx.h>
15#else
16#include "base/message_loop/message_pump_x11.h"
17#endif
18
19using content::BrowserThread;
20
21namespace {
22
23static base::LazyInstance<extensions::GlobalShortcutListenerX11> instance =
24    LAZY_INSTANCE_INITIALIZER;
25
26// The modifiers masks used for grabing keys. Due to XGrabKey only working on
27// exact modifiers, we need to grab all key combination including zero or more
28// of the following: Num lock, Caps lock and Scroll lock. So that we can make
29// sure the behavior of global shortcuts is consistent on all platforms.
30static const unsigned int kModifiersMasks[] = {
31  0,                                // No additional modifier.
32  Mod2Mask,                         // Num lock
33  LockMask,                         // Caps lock
34  Mod5Mask,                         // Scroll lock
35  Mod2Mask | LockMask,
36  Mod2Mask | Mod5Mask,
37  LockMask | Mod5Mask,
38  Mod2Mask | LockMask | Mod5Mask
39};
40
41int GetNativeModifiers(const ui::Accelerator& accelerator) {
42  int modifiers = 0;
43  modifiers |= accelerator.IsShiftDown() ? ShiftMask : 0;
44  modifiers |= accelerator.IsCtrlDown() ? ControlMask : 0;
45  modifiers |= accelerator.IsAltDown() ? Mod1Mask : 0;
46
47  return modifiers;
48}
49
50}  // namespace
51
52namespace extensions {
53
54// static
55GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
56  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
57  return instance.Pointer();
58}
59
60GlobalShortcutListenerX11::GlobalShortcutListenerX11()
61    : is_listening_(false),
62      x_display_(gfx::GetXDisplay()),
63      x_root_window_(DefaultRootWindow(x_display_)) {
64  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
65}
66
67GlobalShortcutListenerX11::~GlobalShortcutListenerX11() {
68  if (is_listening_)
69    StopListening();
70}
71
72void GlobalShortcutListenerX11::StartListening() {
73  DCHECK(!is_listening_);  // Don't start twice.
74  DCHECK(!registered_hot_keys_.empty());  // Also don't start if no hotkey is
75                                          // registered.
76#if defined(TOOLKIT_GTK)
77  gdk_window_add_filter(gdk_get_default_root_window(),
78                        &GlobalShortcutListenerX11::OnXEventThunk,
79                        this);
80#else
81  base::MessagePumpX11::Current()->AddDispatcherForRootWindow(this);
82#endif
83
84  is_listening_ = true;
85}
86
87void GlobalShortcutListenerX11::StopListening() {
88  DCHECK(is_listening_);  // No point if we are not already listening.
89  DCHECK(registered_hot_keys_.empty());  // Make sure the set is clean before
90                                         // ending.
91
92#if defined(TOOLKIT_GTK)
93  gdk_window_remove_filter(NULL,
94                           &GlobalShortcutListenerX11::OnXEventThunk,
95                           this);
96#else
97  base::MessagePumpX11::Current()->RemoveDispatcherForRootWindow(this);
98#endif
99
100  is_listening_ = false;
101}
102
103bool GlobalShortcutListenerX11::Dispatch(const base::NativeEvent& event) {
104  if (event->type == KeyPress)
105    OnXKeyPressEvent(event);
106
107  return true;
108}
109
110void GlobalShortcutListenerX11::RegisterAccelerator(
111    const ui::Accelerator& accelerator,
112    GlobalShortcutListener::Observer* observer) {
113  if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end()) {
114    // The shortcut has already been registered. Some shortcuts, such as
115    // MediaKeys can have multiple targets, all keyed off of the same
116    // accelerator.
117    return;
118  }
119
120  int modifiers = GetNativeModifiers(accelerator);
121  KeyCode keycode = XKeysymToKeycode(x_display_, accelerator.key_code());
122  base::X11ErrorTracker err_tracker;
123
124  // Because XGrabKey only works on the exact modifiers mask, we should register
125  // our hot keys with modifiers that we want to ignore, including Num lock,
126  // Caps lock, Scroll lock. See comment about |kModifiersMasks|.
127  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
128    XGrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
129             x_root_window_, False, GrabModeAsync, GrabModeAsync);
130  }
131
132  if (err_tracker.FoundNewError()) {
133    LOG(ERROR) << "X failed to grab global hotkey: "
134               << accelerator.GetShortcutText();
135
136    // We may have part of the hotkeys registered, clean up.
137    for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
138      XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
139                 x_root_window_);
140    }
141  } else {
142    registered_hot_keys_.insert(accelerator);
143    GlobalShortcutListener::RegisterAccelerator(accelerator, observer);
144  }
145}
146
147void GlobalShortcutListenerX11::UnregisterAccelerator(
148    const ui::Accelerator& accelerator,
149    GlobalShortcutListener::Observer* observer) {
150  if (registered_hot_keys_.find(accelerator) == registered_hot_keys_.end())
151    return;
152
153  int modifiers = GetNativeModifiers(accelerator);
154  KeyCode keycode = XKeysymToKeycode(x_display_, accelerator.key_code());
155
156  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
157    XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
158               x_root_window_);
159  }
160  registered_hot_keys_.erase(accelerator);
161  GlobalShortcutListener::UnregisterAccelerator(accelerator, observer);
162}
163
164#if defined(TOOLKIT_GTK)
165GdkFilterReturn GlobalShortcutListenerX11::OnXEvent(GdkXEvent* gdk_x_event,
166                                                    GdkEvent* gdk_event) {
167  XEvent* x_event = static_cast<XEvent*>(gdk_x_event);
168  if (x_event->type == KeyPress)
169    OnXKeyPressEvent(x_event);
170
171  return GDK_FILTER_CONTINUE;
172}
173#endif
174
175void GlobalShortcutListenerX11::OnXKeyPressEvent(::XEvent* x_event) {
176  DCHECK(x_event->type == KeyPress);
177  int modifiers = 0;
178  modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0;
179  modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0;
180  modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0;
181
182  ui::Accelerator accelerator(
183      ui::KeyboardCodeFromXKeyEvent(x_event), modifiers);
184  if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end())
185    instance.Get().NotifyKeyPressed(accelerator);
186}
187
188}  // namespace extensions
189