xkeyboard.cc revision 8bcbed890bc3ce4d7a057a8f32cab53fa534672e
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 "chromeos/ime/xkeyboard.h"
6
7#include <cstdlib>
8#include <cstring>
9#include <queue>
10#include <set>
11#include <utility>
12
13#include "base/logging.h"
14#include "base/memory/scoped_ptr.h"
15#include "base/message_loop/message_loop.h"
16#include "base/process/launch.h"
17#include "base/process/process_handle.h"
18#include "base/strings/string_util.h"
19#include "base/strings/stringprintf.h"
20#include "base/sys_info.h"
21#include "base/threading/thread_checker.h"
22
23// These includes conflict with base/tracked_objects.h so must come last.
24#include <X11/XKBlib.h>
25#include <X11/Xlib.h>
26#include <glib.h>
27
28namespace chromeos {
29namespace input_method {
30namespace {
31
32Display* GetXDisplay() {
33  return base::MessagePumpForUI::GetDefaultXDisplay();
34}
35
36// The command we use to set the current XKB layout and modifier key mapping.
37// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
38const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
39
40// A string for obtaining a mask value for Num Lock.
41const char kNumLockVirtualModifierString[] = "NumLock";
42
43// Returns false if |layout_name| contains a bad character.
44bool CheckLayoutName(const std::string& layout_name) {
45  static const char kValidLayoutNameCharacters[] =
46      "abcdefghijklmnopqrstuvwxyz0123456789()-_";
47
48  if (layout_name.empty()) {
49    DVLOG(1) << "Invalid layout_name: " << layout_name;
50    return false;
51  }
52
53  if (layout_name.find_first_not_of(kValidLayoutNameCharacters) !=
54      std::string::npos) {
55    DVLOG(1) << "Invalid layout_name: " << layout_name;
56    return false;
57  }
58
59  return true;
60}
61
62class XKeyboardImpl : public XKeyboard {
63 public:
64  XKeyboardImpl();
65  virtual ~XKeyboardImpl() {}
66
67  // Overridden from XKeyboard:
68  virtual bool SetCurrentKeyboardLayoutByName(
69      const std::string& layout_name) OVERRIDE;
70  virtual bool ReapplyCurrentKeyboardLayout() OVERRIDE;
71  virtual void ReapplyCurrentModifierLockStatus() OVERRIDE;
72  virtual void SetLockedModifiers(
73      ModifierLockStatus new_caps_lock_status,
74      ModifierLockStatus new_num_lock_status) OVERRIDE;
75  virtual void SetNumLockEnabled(bool enable_num_lock) OVERRIDE;
76  virtual void SetCapsLockEnabled(bool enable_caps_lock) OVERRIDE;
77  virtual bool NumLockIsEnabled() OVERRIDE;
78  virtual bool CapsLockIsEnabled() OVERRIDE;
79  virtual unsigned int GetNumLockMask() OVERRIDE;
80  virtual void GetLockedModifiers(bool* out_caps_lock_enabled,
81                                  bool* out_num_lock_enabled) OVERRIDE;
82
83 private:
84  // This function is used by SetLayout() and RemapModifierKeys(). Calls
85  // setxkbmap command if needed, and updates the last_full_layout_name_ cache.
86  bool SetLayoutInternal(const std::string& layout_name, bool force);
87
88  // Executes 'setxkbmap -layout ...' command asynchronously using a layout name
89  // in the |execute_queue_|. Do nothing if the queue is empty.
90  // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
91  void MaybeExecuteSetLayoutCommand();
92
93  // Called when execve'd setxkbmap process exits.
94  static void OnSetLayoutFinish(pid_t pid, int status, XKeyboardImpl* self);
95
96  const bool is_running_on_chrome_os_;
97  unsigned int num_lock_mask_;
98
99  // The current Num Lock and Caps Lock status. If true, enabled.
100  bool current_num_lock_status_;
101  bool current_caps_lock_status_;
102  // The XKB layout name which we set last time like "us" and "us(dvorak)".
103  std::string current_layout_name_;
104
105  // A queue for executing setxkbmap one by one.
106  std::queue<std::string> execute_queue_;
107
108  base::ThreadChecker thread_checker_;
109
110  DISALLOW_COPY_AND_ASSIGN(XKeyboardImpl);
111};
112
113XKeyboardImpl::XKeyboardImpl()
114    : is_running_on_chrome_os_(base::SysInfo::IsRunningOnChromeOS()) {
115  num_lock_mask_ = GetNumLockMask();
116
117  // web_input_event_aurax11.cc seems to assume that Mod2Mask is always assigned
118  // to Num Lock.
119  // TODO(yusukes): Check the assumption is really okay. If not, modify the Aura
120  // code, and then remove the CHECK below.
121  CHECK(!is_running_on_chrome_os_ || (num_lock_mask_ == Mod2Mask));
122  GetLockedModifiers(&current_caps_lock_status_, &current_num_lock_status_);
123}
124
125bool XKeyboardImpl::SetLayoutInternal(const std::string& layout_name,
126                                      bool force) {
127  if (!is_running_on_chrome_os_) {
128    // We should not try to change a layout on Linux or inside ui_tests. Just
129    // return true.
130    return true;
131  }
132
133  if (!CheckLayoutName(layout_name))
134    return false;
135
136  if (!force && (current_layout_name_ == layout_name)) {
137    DVLOG(1) << "The requested layout is already set: " << layout_name;
138    return true;
139  }
140
141  DVLOG(1) << (force ? "Reapply" : "Set") << " layout: " << layout_name;
142
143  const bool start_execution = execute_queue_.empty();
144  // If no setxkbmap command is in flight (i.e. start_execution is true),
145  // start the first one by explicitly calling MaybeExecuteSetLayoutCommand().
146  // If one or more setxkbmap commands are already in flight, just push the
147  // layout name to the queue. setxkbmap command for the layout will be called
148  // via OnSetLayoutFinish() callback later.
149  execute_queue_.push(layout_name);
150  if (start_execution)
151    MaybeExecuteSetLayoutCommand();
152
153  return true;
154}
155
156// Executes 'setxkbmap -layout ...' command asynchronously using a layout name
157// in the |execute_queue_|. Do nothing if the queue is empty.
158// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
159void XKeyboardImpl::MaybeExecuteSetLayoutCommand() {
160  if (execute_queue_.empty())
161    return;
162  const std::string layout_to_set = execute_queue_.front();
163
164  std::vector<std::string> argv;
165  base::ProcessHandle handle = base::kNullProcessHandle;
166
167  argv.push_back(kSetxkbmapCommand);
168  argv.push_back("-layout");
169  argv.push_back(layout_to_set);
170  argv.push_back("-synch");
171
172  if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) {
173    DVLOG(1) << "Failed to execute setxkbmap: " << layout_to_set;
174    execute_queue_ = std::queue<std::string>();  // clear the queue.
175    return;
176  }
177
178  // g_child_watch_add is necessary to prevent the process from becoming a
179  // zombie.
180  const base::ProcessId pid = base::GetProcId(handle);
181  g_child_watch_add(pid,
182                    reinterpret_cast<GChildWatchFunc>(OnSetLayoutFinish),
183                    this);
184  DVLOG(1) << "ExecuteSetLayoutCommand: " << layout_to_set << ": pid=" << pid;
185}
186
187bool XKeyboardImpl::NumLockIsEnabled() {
188  bool num_lock_enabled = false;
189  GetLockedModifiers(NULL /* Caps Lock */, &num_lock_enabled);
190  return num_lock_enabled;
191}
192
193bool XKeyboardImpl::CapsLockIsEnabled() {
194  bool caps_lock_enabled = false;
195  GetLockedModifiers(&caps_lock_enabled, NULL /* Num Lock */);
196  return caps_lock_enabled;
197}
198
199unsigned int XKeyboardImpl::GetNumLockMask() {
200  DCHECK(thread_checker_.CalledOnValidThread());
201  static const unsigned int kBadMask = 0;
202
203  unsigned int real_mask = kBadMask;
204  XkbDescPtr xkb_desc =
205      XkbGetKeyboard(GetXDisplay(), XkbAllComponentsMask, XkbUseCoreKbd);
206  if (!xkb_desc)
207    return kBadMask;
208
209  if (xkb_desc->dpy && xkb_desc->names && xkb_desc->names->vmods) {
210    const std::string string_to_find(kNumLockVirtualModifierString);
211    for (size_t i = 0; i < XkbNumVirtualMods; ++i) {
212      const unsigned int virtual_mod_mask = 1U << i;
213      char* virtual_mod_str_raw_ptr =
214          XGetAtomName(xkb_desc->dpy, xkb_desc->names->vmods[i]);
215      if (!virtual_mod_str_raw_ptr)
216        continue;
217      const std::string virtual_mod_str = virtual_mod_str_raw_ptr;
218      XFree(virtual_mod_str_raw_ptr);
219
220      if (string_to_find == virtual_mod_str) {
221        if (!XkbVirtualModsToReal(xkb_desc, virtual_mod_mask, &real_mask)) {
222          DVLOG(1) << "XkbVirtualModsToReal failed";
223          real_mask = kBadMask;  // reset the return value, just in case.
224        }
225        break;
226      }
227    }
228  }
229  XkbFreeKeyboard(xkb_desc, 0, True /* free all components */);
230  return real_mask;
231}
232
233void XKeyboardImpl::GetLockedModifiers(bool* out_caps_lock_enabled,
234                                       bool* out_num_lock_enabled) {
235  DCHECK(thread_checker_.CalledOnValidThread());
236
237  if (out_num_lock_enabled && !num_lock_mask_) {
238    DVLOG(1) << "Cannot get locked modifiers. Num Lock mask unknown.";
239    if (out_caps_lock_enabled)
240      *out_caps_lock_enabled = false;
241    if (out_num_lock_enabled)
242      *out_num_lock_enabled = false;
243    return;
244  }
245
246  XkbStateRec status;
247  XkbGetState(GetXDisplay(), XkbUseCoreKbd, &status);
248  if (out_caps_lock_enabled)
249    *out_caps_lock_enabled = status.locked_mods & LockMask;
250  if (out_num_lock_enabled)
251    *out_num_lock_enabled = status.locked_mods & num_lock_mask_;
252}
253
254void XKeyboardImpl::SetLockedModifiers(ModifierLockStatus new_caps_lock_status,
255                                       ModifierLockStatus new_num_lock_status) {
256  DCHECK(thread_checker_.CalledOnValidThread());
257  if (!num_lock_mask_) {
258    DVLOG(1) << "Cannot set locked modifiers. Num Lock mask unknown.";
259    return;
260  }
261
262  unsigned int affect_mask = 0;
263  unsigned int value_mask = 0;
264  if (new_caps_lock_status != kDontChange) {
265    affect_mask |= LockMask;
266    value_mask |= ((new_caps_lock_status == kEnableLock) ? LockMask : 0);
267    current_caps_lock_status_ = (new_caps_lock_status == kEnableLock);
268  }
269  if (new_num_lock_status != kDontChange) {
270    affect_mask |= num_lock_mask_;
271    value_mask |= ((new_num_lock_status == kEnableLock) ? num_lock_mask_ : 0);
272    current_num_lock_status_ = (new_num_lock_status == kEnableLock);
273  }
274
275  if (affect_mask)
276    XkbLockModifiers(GetXDisplay(), XkbUseCoreKbd, affect_mask, value_mask);
277}
278
279void XKeyboardImpl::SetNumLockEnabled(bool enable_num_lock) {
280  SetLockedModifiers(
281      kDontChange, enable_num_lock ? kEnableLock : kDisableLock);
282}
283
284void XKeyboardImpl::SetCapsLockEnabled(bool enable_caps_lock) {
285  SetLockedModifiers(
286      enable_caps_lock ? kEnableLock : kDisableLock, kDontChange);
287}
288
289bool XKeyboardImpl::SetCurrentKeyboardLayoutByName(
290    const std::string& layout_name) {
291  if (SetLayoutInternal(layout_name, false)) {
292    current_layout_name_ = layout_name;
293    return true;
294  }
295  return false;
296}
297
298bool XKeyboardImpl::ReapplyCurrentKeyboardLayout() {
299  if (current_layout_name_.empty()) {
300    DVLOG(1) << "Can't reapply XKB layout: layout unknown";
301    return false;
302  }
303  return SetLayoutInternal(current_layout_name_, true /* force */);
304}
305
306void XKeyboardImpl::ReapplyCurrentModifierLockStatus() {
307  SetLockedModifiers(current_caps_lock_status_ ? kEnableLock : kDisableLock,
308                     current_num_lock_status_ ? kEnableLock : kDisableLock);
309}
310
311// static
312void XKeyboardImpl::OnSetLayoutFinish(pid_t pid,
313                                      int status,
314                                      XKeyboardImpl* self) {
315  DVLOG(1) << "OnSetLayoutFinish: pid=" << pid;
316  if (self->execute_queue_.empty()) {
317    DVLOG(1) << "OnSetLayoutFinish: execute_queue_ is empty. "
318             << "base::LaunchProcess failed? pid=" << pid;
319    return;
320  }
321  self->execute_queue_.pop();
322  self->MaybeExecuteSetLayoutCommand();
323}
324
325}  // namespace
326
327// static
328bool XKeyboard::SetAutoRepeatEnabled(bool enabled) {
329  if (enabled)
330    XAutoRepeatOn(GetXDisplay());
331  else
332    XAutoRepeatOff(GetXDisplay());
333  DVLOG(1) << "Set auto-repeat mode to: " << (enabled ? "on" : "off");
334  return true;
335}
336
337// static
338bool XKeyboard::SetAutoRepeatRate(const AutoRepeatRate& rate) {
339  DVLOG(1) << "Set auto-repeat rate to: "
340           << rate.initial_delay_in_ms << " ms delay, "
341           << rate.repeat_interval_in_ms << " ms interval";
342  if (XkbSetAutoRepeatRate(GetXDisplay(), XkbUseCoreKbd,
343                           rate.initial_delay_in_ms,
344                           rate.repeat_interval_in_ms) != True) {
345    DVLOG(1) << "Failed to set auto-repeat rate";
346    return false;
347  }
348  return true;
349}
350
351// static
352bool XKeyboard::GetAutoRepeatEnabledForTesting() {
353  XKeyboardState state = {};
354  XGetKeyboardControl(GetXDisplay(), &state);
355  return state.global_auto_repeat != AutoRepeatModeOff;
356}
357
358// static
359bool XKeyboard::GetAutoRepeatRateForTesting(AutoRepeatRate* out_rate) {
360  return XkbGetAutoRepeatRate(GetXDisplay(), XkbUseCoreKbd,
361                              &(out_rate->initial_delay_in_ms),
362                              &(out_rate->repeat_interval_in_ms)) == True;
363}
364
365// static
366bool XKeyboard::CheckLayoutNameForTesting(const std::string& layout_name) {
367  return CheckLayoutName(layout_name);
368}
369
370// static
371XKeyboard* XKeyboard::Create() {
372  return new XKeyboardImpl();
373}
374
375}  // namespace input_method
376}  // namespace chromeos
377