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