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