Home | History | Annotate | Download | only in extensions
      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 
     19 using content::BrowserThread;
     20 
     21 namespace {
     22 
     23 static 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.
     30 static 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 
     41 int 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 
     52 namespace extensions {
     53 
     54 // static
     55 GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
     56   CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     57   return instance.Pointer();
     58 }
     59 
     60 GlobalShortcutListenerX11::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 
     67 GlobalShortcutListenerX11::~GlobalShortcutListenerX11() {
     68   if (is_listening_)
     69     StopListening();
     70 }
     71 
     72 void 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 
     87 void 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 
    103 bool GlobalShortcutListenerX11::Dispatch(const base::NativeEvent& event) {
    104   if (event->type == KeyPress)
    105     OnXKeyPressEvent(event);
    106 
    107   return true;
    108 }
    109 
    110 void 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 
    147 void 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)
    165 GdkFilterReturn 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 
    175 void 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