Index: chrome/browser/extensions/global_shortcut_listener_gtk.cc |
diff --git a/chrome/browser/extensions/global_shortcut_listener_gtk.cc b/chrome/browser/extensions/global_shortcut_listener_gtk.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3000219375dd9a83730a6bf8d6b5720e2013334f |
--- /dev/null |
+++ b/chrome/browser/extensions/global_shortcut_listener_gtk.cc |
@@ -0,0 +1,155 @@ |
+// Copyright (c) 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_gtk.h" |
+ |
+#include <gdk/gdkx.h> |
+#include <X11/Xlib.h> |
+ |
+#include "base/x11/x11_error_tracker.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "ui/base/accelerators/accelerator.h" |
+#include "ui/events/keycodes/keyboard_code_conversion_x.h" |
+ |
+using content::BrowserThread; |
+ |
+namespace { |
+ |
+static base::LazyInstance<extensions::GlobalShortcutListenerGtk> instance = |
+ LAZY_INSTANCE_INITIALIZER; |
+ |
+// 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. |
+static const unsigned int kModifiersMasks[] = { |
+ 0, // No additional modifier. |
+ Mod2Mask, // Num lock |
+ LockMask, // Caps lock |
+ Mod5Mask, // Scroll lock |
+ Mod2Mask | LockMask, |
+ Mod2Mask | Mod5Mask, |
+ LockMask | Mod5Mask, |
+ Mod2Mask | LockMask | Mod5Mask |
+}; |
+ |
+int GetNativeModifiers(const ui::Accelerator& accelerator) { |
+ int modifiers = 0; |
+ modifiers |= accelerator.IsShiftDown() ? ShiftMask : 0; |
+ modifiers |= accelerator.IsCtrlDown() ? ControlMask : 0; |
+ modifiers |= accelerator.IsAltDown() ? Mod1Mask : 0; |
+ |
+ return modifiers; |
+} |
+ |
+} // namespace |
+ |
+namespace extensions { |
+ |
+// static |
+GlobalShortcutListener* GlobalShortcutListener::GetInstance() { |
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+ return instance.Pointer(); |
+} |
+ |
+GlobalShortcutListenerGtk::GlobalShortcutListenerGtk() |
+ : is_listening_(false) { |
+ CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
+} |
+ |
+GlobalShortcutListenerGtk::~GlobalShortcutListenerGtk() { |
+ if (is_listening_) |
+ StopListening(); |
+} |
+ |
+void GlobalShortcutListenerGtk::StartListening() { |
+ DCHECK(!is_listening_); // Don't start twice. |
+ DCHECK(!registered_hot_keys_.empty()); // Also don't start if no hotkey is |
+ // registered. |
+ |
+ gdk_window_add_filter(gdk_get_default_root_window(), |
+ &GlobalShortcutListenerGtk::OnXEventThunk, |
+ this); |
+ is_listening_ = true; |
+} |
+ |
+void GlobalShortcutListenerGtk::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. |
+ |
+ gdk_window_remove_filter(NULL, |
+ &GlobalShortcutListenerGtk::OnXEventThunk, |
+ this); |
+ is_listening_ = false; |
+} |
+ |
+void GlobalShortcutListenerGtk::RegisterAccelerator( |
+ const ui::Accelerator& accelerator, |
+ GlobalShortcutListener::Observer* observer) { |
+ Display* display = GDK_WINDOW_XDISPLAY(gdk_get_default_root_window()); |
+ int modifiers = GetNativeModifiers(accelerator); |
+ KeyCode keycode = XKeysymToKeycode(display, accelerator.key_code()); |
+ base::X11ErrorTracker err_tracker; |
+ |
+ // 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|. |
+ for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) { |
+ XGrabKey(display, keycode, modifiers | kModifiersMasks[i], |
+ gdk_x11_get_default_root_xwindow(), False, |
+ GrabModeAsync, GrabModeAsync); |
+ } |
+ |
+ if (err_tracker.FoundNewError()) { |
+ LOG(ERROR) << "X failed to grab global hotkey: " |
+ << accelerator.GetShortcutText(); |
+ |
+ // We may have part of the hotkeys registered, clean up. |
+ for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) { |
+ XUngrabKey(display, keycode, modifiers | kModifiersMasks[i], |
+ gdk_x11_get_default_root_xwindow()); |
+ } |
+ } else { |
+ registered_hot_keys_.insert(accelerator); |
+ GlobalShortcutListener::RegisterAccelerator(accelerator, observer); |
+ } |
+} |
+ |
+void GlobalShortcutListenerGtk::UnregisterAccelerator( |
+ const ui::Accelerator& accelerator, |
+ GlobalShortcutListener::Observer* observer) { |
+ if (registered_hot_keys_.find(accelerator) == registered_hot_keys_.end()) |
+ return; |
+ |
+ Display* display = GDK_WINDOW_XDISPLAY(gdk_get_default_root_window()); |
+ int modifiers = GetNativeModifiers(accelerator); |
+ KeyCode keycode = XKeysymToKeycode(display, accelerator.key_code()); |
+ |
+ for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) { |
+ XUngrabKey(display, keycode, modifiers | kModifiersMasks[i], |
+ gdk_x11_get_default_root_xwindow()); |
+ } |
+ registered_hot_keys_.erase(accelerator); |
+ GlobalShortcutListener::UnregisterAccelerator(accelerator, observer); |
+} |
+ |
+GdkFilterReturn GlobalShortcutListenerGtk::OnXEvent(GdkXEvent* gdk_x_event, |
+ GdkEvent* gdk_event) { |
+ XEvent* x_event = static_cast<XEvent*>(gdk_x_event); |
+ if (x_event->type == KeyPress) { |
+ int modifiers = 0; |
+ modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0; |
+ modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0; |
+ modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0; |
+ |
+ ui::Accelerator accelerator( |
+ ui::KeyboardCodeFromXKeyEvent(x_event), modifiers); |
+ instance.Get().NotifyKeyPressed(accelerator); |
+ } |
+ |
+ return GDK_FILTER_CONTINUE; |
+} |
+ |
+} // namespace extensions |