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 14 using content::BrowserThread; 15 16 namespace { 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. 22 const 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 33 int 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 44 namespace extensions { 45 46 // static 47 GlobalShortcutListener* GlobalShortcutListener::GetInstance() { 48 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 49 static GlobalShortcutListenerX11* instance = 50 new GlobalShortcutListenerX11(); 51 return instance; 52 } 53 54 GlobalShortcutListenerX11::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 61 GlobalShortcutListenerX11::~GlobalShortcutListenerX11() { 62 if (is_listening_) 63 StopListening(); 64 } 65 66 void 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 76 void 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 86 bool GlobalShortcutListenerX11::CanDispatchEvent( 87 const ui::PlatformEvent& event) { 88 return event->type == KeyPress; 89 } 90 91 uint32_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 99 bool 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 130 void 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 145 void 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