14e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
24e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
34e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// found in the LICENSE file.
44e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
54e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "chrome/browser/extensions/global_shortcut_listener_x11.h"
64e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
74e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "content/public/browser/browser_thread.h"
84e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "ui/base/accelerators/accelerator.h"
94e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "ui/events/keycodes/keyboard_code_conversion_x.h"
10a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch#include "ui/events/platform/x11/x11_event_source.h"
1123730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include "ui/gfx/x/x11_error_tracker.h"
124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#include "ui/gfx/x/x11_types.h"
134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)using content::BrowserThread;
154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)namespace {
174e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// The modifiers masks used for grabing keys. Due to XGrabKey only working on
194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// exact modifiers, we need to grab all key combination including zero or more
204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// of the following: Num lock, Caps lock and Scroll lock. So that we can make
214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// sure the behavior of global shortcuts is consistent on all platforms.
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)const unsigned int kModifiersMasks[] = {
234e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  0,                                // No additional modifier.
244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Mod2Mask,                         // Num lock
254e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  LockMask,                         // Caps lock
264e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Mod5Mask,                         // Scroll lock
274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Mod2Mask | LockMask,
284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Mod2Mask | Mod5Mask,
294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  LockMask | Mod5Mask,
304e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  Mod2Mask | LockMask | Mod5Mask
314e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)};
324e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)int GetNativeModifiers(const ui::Accelerator& accelerator) {
344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int modifiers = 0;
354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  modifiers |= accelerator.IsShiftDown() ? ShiftMask : 0;
364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  modifiers |= accelerator.IsCtrlDown() ? ControlMask : 0;
374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  modifiers |= accelerator.IsAltDown() ? Mod1Mask : 0;
384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  return modifiers;
404e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
414e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}  // namespace
434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)namespace extensions {
454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)// static
474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)GlobalShortcutListener* GlobalShortcutListener::GetInstance() {
484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  static GlobalShortcutListenerX11* instance =
505d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      new GlobalShortcutListenerX11();
515d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return instance;
524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)GlobalShortcutListenerX11::GlobalShortcutListenerX11()
554e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    : is_listening_(false),
564e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      x_display_(gfx::GetXDisplay()),
574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      x_root_window_(DefaultRootWindow(x_display_)) {
584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
594e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
604e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
614e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)GlobalShortcutListenerX11::~GlobalShortcutListenerX11() {
624e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (is_listening_)
634e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    StopListening();
644e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
654e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
664e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void GlobalShortcutListenerX11::StartListening() {
674e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  DCHECK(!is_listening_);  // Don't start twice.
684e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  DCHECK(!registered_hot_keys_.empty());  // Also don't start if no hotkey is
694e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                          // registered.
70a02191e04bc25c4935f804f2c080ae28663d096dBen Murdoch
71c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  ui::X11EventSource::GetInstance()->AddPlatformEventDispatcher(this);
724e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
734e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  is_listening_ = true;
744e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
754e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
764e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void GlobalShortcutListenerX11::StopListening() {
774e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  DCHECK(is_listening_);  // No point if we are not already listening.
784e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  DCHECK(registered_hot_keys_.empty());  // Make sure the set is clean before
794e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                                         // ending.
804e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
81c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  ui::X11EventSource::GetInstance()->RemovePlatformEventDispatcher(this);
824e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
834e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  is_listening_ = false;
844e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
854e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
86c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochbool GlobalShortcutListenerX11::CanDispatchEvent(
87c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    const ui::PlatformEvent& event) {
88c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  return event->type == KeyPress;
89c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch}
90c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch
91c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdochuint32_t GlobalShortcutListenerX11::DispatchEvent(
92c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch    const ui::PlatformEvent& event) {
93c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  CHECK_EQ(KeyPress, event->type);
94c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  OnXKeyPressEvent(event);
954e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
96c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch  return ui::POST_DISPATCH_NONE;
974e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
984e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)bool GlobalShortcutListenerX11::RegisterAcceleratorImpl(
1005d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const ui::Accelerator& accelerator) {
1015d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK(registered_hot_keys_.find(accelerator) == registered_hot_keys_.end());
102f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1034e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int modifiers = GetNativeModifiers(accelerator);
1045d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  KeyCode keycode = XKeysymToKeycode(x_display_,
1055d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      XKeysymForWindowsKeyCode(accelerator.key_code(), false));
10623730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)  gfx::X11ErrorTracker err_tracker;
1074e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1084e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // Because XGrabKey only works on the exact modifiers mask, we should register
1094e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // our hot keys with modifiers that we want to ignore, including Num lock,
1104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  // Caps lock, Scroll lock. See comment about |kModifiersMasks|.
1114e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
1124e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    XGrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
1134e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)             x_root_window_, False, GrabModeAsync, GrabModeAsync);
1144e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1154e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1164e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (err_tracker.FoundNewError()) {
1174e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    // We may have part of the hotkeys registered, clean up.
1184e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
1194e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
1204e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)                 x_root_window_);
1214e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    }
1225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1235d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    return false;
1244e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1255d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
1265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  registered_hot_keys_.insert(accelerator);
1275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  return true;
1284e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
1294e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void GlobalShortcutListenerX11::UnregisterAcceleratorImpl(
1315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    const ui::Accelerator& accelerator) {
1325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  DCHECK(registered_hot_keys_.find(accelerator) != registered_hot_keys_.end());
1334e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1344e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int modifiers = GetNativeModifiers(accelerator);
1355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  KeyCode keycode = XKeysymToKeycode(x_display_,
1365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      XKeysymForWindowsKeyCode(accelerator.key_code(), false));
1374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1384e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  for (size_t i = 0; i < arraysize(kModifiersMasks); ++i) {
1394e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    XUngrabKey(x_display_, keycode, modifiers | kModifiersMasks[i],
1404e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)               x_root_window_);
1414e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  }
1424e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  registered_hot_keys_.erase(accelerator);
1434e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
1444e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1454e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)void GlobalShortcutListenerX11::OnXKeyPressEvent(::XEvent* x_event) {
1464e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  DCHECK(x_event->type == KeyPress);
1474e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  int modifiers = 0;
1484e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  modifiers |= (x_event->xkey.state & ShiftMask) ? ui::EF_SHIFT_DOWN : 0;
1494e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  modifiers |= (x_event->xkey.state & ControlMask) ? ui::EF_CONTROL_DOWN : 0;
1504e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  modifiers |= (x_event->xkey.state & Mod1Mask) ? ui::EF_ALT_DOWN : 0;
1514e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1524e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  ui::Accelerator accelerator(
1534e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)      ui::KeyboardCodeFromXKeyEvent(x_event), modifiers);
1544e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)  if (registered_hot_keys_.find(accelerator) != registered_hot_keys_.end())
1555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    NotifyKeyPressed(accelerator);
1564e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}
1574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
1584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)}  // namespace extensions
159