1// Copyright (c) 2012 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/chromeos/system_key_event_listener.h"
6
7#define XK_MISCELLANY 1
8#include <X11/keysymdef.h>
9#include <X11/XF86keysym.h>
10#include <X11/XKBlib.h>
11#undef Status
12
13#include "base/message_loop/message_loop.h"
14#include "chromeos/ime/input_method_manager.h"
15#include "chromeos/ime/xkeyboard.h"
16#include "ui/base/x/x11_util.h"
17
18namespace chromeos {
19
20namespace {
21static SystemKeyEventListener* g_system_key_event_listener = NULL;
22}  // namespace
23
24// static
25void SystemKeyEventListener::Initialize() {
26  CHECK(!g_system_key_event_listener);
27  g_system_key_event_listener = new SystemKeyEventListener();
28}
29
30// static
31void SystemKeyEventListener::Shutdown() {
32  // We may call Shutdown without calling Initialize, e.g. if we exit early.
33  if (g_system_key_event_listener) {
34    delete g_system_key_event_listener;
35    g_system_key_event_listener = NULL;
36  }
37}
38
39// static
40SystemKeyEventListener* SystemKeyEventListener::GetInstance() {
41  return g_system_key_event_listener;
42}
43
44SystemKeyEventListener::SystemKeyEventListener()
45    : stopped_(false),
46      num_lock_mask_(0),
47      pressed_modifiers_(0),
48      xkb_event_base_(0) {
49  input_method::XKeyboard* xkeyboard =
50      input_method::InputMethodManager::Get()->GetXKeyboard();
51  num_lock_mask_ = xkeyboard->GetNumLockMask();
52  xkeyboard->GetLockedModifiers(&caps_lock_is_on_, NULL);
53
54  Display* display = ui::GetXDisplay();
55  int xkb_major_version = XkbMajorVersion;
56  int xkb_minor_version = XkbMinorVersion;
57  if (!XkbQueryExtension(display,
58                         NULL,  // opcode_return
59                         &xkb_event_base_,
60                         NULL,  // error_return
61                         &xkb_major_version,
62                         &xkb_minor_version)) {
63    LOG(WARNING) << "Could not query Xkb extension";
64  }
65
66  if (!XkbSelectEvents(display, XkbUseCoreKbd,
67                       XkbStateNotifyMask,
68                       XkbStateNotifyMask)) {
69    LOG(WARNING) << "Could not install Xkb Indicator observer";
70  }
71
72  base::MessageLoopForUI::current()->AddObserver(this);
73}
74
75SystemKeyEventListener::~SystemKeyEventListener() {
76  Stop();
77}
78
79void SystemKeyEventListener::Stop() {
80  if (stopped_)
81    return;
82  base::MessageLoopForUI::current()->RemoveObserver(this);
83  stopped_ = true;
84}
85
86void SystemKeyEventListener::AddCapsLockObserver(CapsLockObserver* observer) {
87  caps_lock_observers_.AddObserver(observer);
88}
89
90void SystemKeyEventListener::AddModifiersObserver(ModifiersObserver* observer) {
91  modifiers_observers_.AddObserver(observer);
92}
93
94void SystemKeyEventListener::RemoveCapsLockObserver(
95    CapsLockObserver* observer) {
96  caps_lock_observers_.RemoveObserver(observer);
97}
98
99void SystemKeyEventListener::RemoveModifiersObserver(
100    ModifiersObserver* observer) {
101  modifiers_observers_.RemoveObserver(observer);
102}
103
104base::EventStatus SystemKeyEventListener::WillProcessEvent(
105    const base::NativeEvent& event) {
106  return ProcessedXEvent(event) ? base::EVENT_HANDLED : base::EVENT_CONTINUE;
107}
108
109void SystemKeyEventListener::DidProcessEvent(const base::NativeEvent& event) {
110}
111
112void SystemKeyEventListener::OnCapsLock(bool enabled) {
113  FOR_EACH_OBSERVER(CapsLockObserver,
114                    caps_lock_observers_,
115                    OnCapsLockChange(enabled));
116}
117
118void SystemKeyEventListener::OnModifiers(int state) {
119  FOR_EACH_OBSERVER(ModifiersObserver,
120                    modifiers_observers_,
121                    OnModifiersChange(state));
122}
123
124bool SystemKeyEventListener::ProcessedXEvent(XEvent* xevent) {
125  input_method::InputMethodManager* input_method_manager =
126      input_method::InputMethodManager::Get();
127
128  if (xevent->type == xkb_event_base_) {
129    // TODO(yusukes): Move this part to aura::RootWindowHost.
130    XkbEvent* xkey_event = reinterpret_cast<XkbEvent*>(xevent);
131    if (xkey_event->any.xkb_type == XkbStateNotify) {
132      const bool caps_lock_enabled = (xkey_event->state.locked_mods) & LockMask;
133      if (caps_lock_is_on_ != caps_lock_enabled) {
134        caps_lock_is_on_ = caps_lock_enabled;
135        OnCapsLock(caps_lock_is_on_);
136      }
137      if (xkey_event->state.mods) {
138        // TODO(yusukes,adlr): Let the user know that num lock is unsupported.
139        // Force turning off Num Lock (crosbug.com/29169)
140        input_method_manager->GetXKeyboard()->SetLockedModifiers(
141            input_method::kDontChange  /* caps lock */,
142            input_method::kDisableLock  /* num lock */);
143      }
144      int current_modifiers = 0;
145      if (xkey_event->state.mods & ShiftMask)
146        current_modifiers |= ModifiersObserver::SHIFT_PRESSED;
147      if (xkey_event->state.mods & ControlMask)
148        current_modifiers |= ModifiersObserver::CTRL_PRESSED;
149      if (xkey_event->state.mods & Mod1Mask)
150        current_modifiers |= ModifiersObserver::ALT_PRESSED;
151      if (current_modifiers != pressed_modifiers_) {
152        pressed_modifiers_ = current_modifiers;
153        OnModifiers(pressed_modifiers_);
154      }
155      return true;
156    }
157  }
158  return false;
159}
160
161}  // namespace chromeos
162