1// Copyright (c) 2011 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/input_method/xkeyboard.h"
6
7#include <queue>
8#include <utility>
9
10#include <X11/XKBlib.h>
11#include <X11/Xlib.h>
12#include <glib.h>
13#include <stdlib.h>
14#include <string.h>
15
16#include "base/memory/singleton.h"
17#include "base/logging.h"
18#include "base/string_util.h"
19#include "base/process_util.h"
20#include "chrome/browser/chromeos/cros/cros_library.h"
21#include "chrome/browser/chromeos/input_method/input_method_util.h"
22#include "content/browser/browser_thread.h"
23
24namespace chromeos {
25namespace input_method {
26namespace {
27
28// The default keyboard layout name in the xorg config file.
29const char kDefaultLayoutName[] = "us";
30// The command we use to set the current XKB layout and modifier key mapping.
31// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
32const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
33// See the comment at ModifierKey in the .h file.
34ModifierKey kCustomizableKeys[] = {
35  kSearchKey,
36  kLeftControlKey,
37  kLeftAltKey
38};
39
40// These arrays are generated by 'gen_keyboard_overlay_data.py --altgr'
41// These are the overlay names of layouts that shouldn't
42// remap the right alt key.
43const char* kKeepRightAltOverlays[] = {
44  "el",
45  "ca",
46  "it",
47  "iw",
48  "es_419",
49  "cs",
50  "et",
51  "es",
52  "en_US_altgr_intl",
53  "de_neo",
54  "nl",
55  "no",
56  "tr",
57  "lt",
58  "pt_PT",
59  "en_GB_dvorak",
60  "fr",
61  "bg",
62  "pt_BR",
63  "en_fr_hybrid_CA",
64  "hr",
65  "da",
66  "fi",
67  "fr_CA",
68  "ko",
69  "sv",
70  "sk",
71  "de",
72  "en_GB",
73  "pl",
74  "uk",
75  "sl",
76  "en_US_intl",
77};
78
79// These are the overlay names with caps lock remapped
80const char* kCapsLockRemapped[] = {
81  "de_neo",
82  "en_US_colemak",
83};
84
85
86bool KeepRightAlt(const std::string& layout_name) {
87  for (size_t c = 0; c < arraysize(kKeepRightAltOverlays); ++c) {
88    if (GetKeyboardOverlayId(layout_name) == kKeepRightAltOverlays[c]) {
89      return true;
90    }
91  }
92  return false;
93}
94
95bool KeepCapsLock(const std::string& layout_name) {
96  for (size_t c = 0; c < arraysize(kCapsLockRemapped); ++c) {
97    if (GetKeyboardOverlayId(layout_name) == kCapsLockRemapped[c]) {
98      return true;
99    }
100  }
101  return false;
102}
103
104// This is a wrapper class around Display, that opens and closes X display in
105// the constructor and destructor.
106class ScopedDisplay {
107 public:
108  explicit ScopedDisplay(Display* display) : display_(display) {
109    if (!display_) {
110      LOG(ERROR) << "NULL display_ is passed";
111    }
112  }
113
114  ~ScopedDisplay() {
115    if (display_) {
116      XCloseDisplay(display_);
117    }
118  }
119
120  Display* get() const {
121    return display_;
122  }
123
124 private:
125  Display* display_;
126
127  DISALLOW_COPY_AND_ASSIGN(ScopedDisplay);
128};
129
130// A singleton class which wraps the setxkbmap command.
131class XKeyboard {
132 public:
133  // Returns the singleton instance of the class. Use LeakySingletonTraits.
134  // We don't delete the instance at exit.
135  static XKeyboard* GetInstance() {
136    return Singleton<XKeyboard, LeakySingletonTraits<XKeyboard> >::get();
137  }
138
139  // Sets the current keyboard layout to |layout_name|. This function does not
140  // change the current mapping of the modifier keys. Returns true on success.
141  bool SetLayout(const std::string& layout_name) {
142    if (SetLayoutInternal(layout_name, current_modifier_map_)) {
143      current_layout_name_ = layout_name;
144      return true;
145    }
146    return false;
147  }
148
149  // Remaps modifier keys. This function does not change the current keyboard
150  // layout. Returns true on success.
151  bool RemapModifierKeys(const ModifierMap& modifier_map) {
152    const std::string layout_name = current_layout_name_.empty() ?
153        kDefaultLayoutName : current_layout_name_;
154    if (SetLayoutInternal(layout_name, modifier_map)) {
155      current_layout_name_ = layout_name;
156      current_modifier_map_ = modifier_map;
157      return true;
158    }
159    return false;
160  }
161
162  // Turns on and off the auto-repeat of the keyboard. Returns true on success.
163  // TODO(yusukes): Remove this function.
164  bool SetAutoRepeatEnabled(bool enabled) {
165    ScopedDisplay display(XOpenDisplay(NULL));
166    if (!display.get()) {
167      return false;
168    }
169    if (enabled) {
170      XAutoRepeatOn(display.get());
171    } else {
172      XAutoRepeatOff(display.get());
173    }
174    DLOG(INFO) << "Set auto-repeat mode to: " << (enabled ? "on" : "off");
175    return true;
176  }
177
178  // Sets the auto-repeat rate of the keyboard, initial delay in ms, and repeat
179  // interval in ms.  Returns true on success.
180  // TODO(yusukes): Call this function in non-UI thread or in an idle callback.
181  bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
182    // TODO(yusukes): write auto tests for the function.
183    ScopedDisplay display(XOpenDisplay(NULL));
184    if (!display.get()) {
185      return false;
186    }
187
188    DLOG(INFO) << "Set auto-repeat rate to: "
189               << rate.initial_delay_in_ms << " ms delay, "
190               << rate.repeat_interval_in_ms << " ms interval";
191    if (XkbSetAutoRepeatRate(display.get(), XkbUseCoreKbd,
192                             rate.initial_delay_in_ms,
193                             rate.repeat_interval_in_ms) != True) {
194      LOG(ERROR) << "Failed to set auto-repeat rate";
195      return false;
196    }
197    return true;
198  }
199
200 private:
201  friend struct DefaultSingletonTraits<XKeyboard>;
202
203  XKeyboard() {
204    for (size_t i = 0; i < arraysize(kCustomizableKeys); ++i) {
205      ModifierKey key = kCustomizableKeys[i];
206      current_modifier_map_.push_back(ModifierKeyPair(key, key));
207    }
208  }
209  ~XKeyboard() {
210  }
211
212  // This function is used by SetLayout() and RemapModifierKeys(). Calls
213  // setxkbmap command if needed, and updates the last_full_layout_name_ cache.
214  bool SetLayoutInternal(const std::string& layout_name,
215                         const ModifierMap& modifier_map) {
216    if (!CrosLibrary::Get()->EnsureLoaded()) {
217      // We should not try to change a layout inside ui_tests.
218      return false;
219    }
220
221    const std::string layout_to_set = CreateFullXkbLayoutName(
222        layout_name, modifier_map);
223    if (layout_to_set.empty()) {
224      return false;
225    }
226
227    if (!current_layout_name_.empty()) {
228      const std::string current_layout = CreateFullXkbLayoutName(
229          current_layout_name_, current_modifier_map_);
230      if (current_layout == layout_to_set) {
231        DLOG(INFO) << "The requested layout is already set: " << layout_to_set;
232        return true;
233      }
234    }
235
236    // Turn off caps lock if there is no kCapsLockKey in the remapped keys.
237    if (!ContainsModifierKeyAsReplacement(
238            modifier_map, kCapsLockKey)) {
239      SetCapsLockEnabled(false);
240    }
241
242    // TODO(yusukes): Revert to VLOG(1) when crosbug.com/15851 is resolved.
243    LOG(WARNING) << "Set layout: " << layout_to_set;
244
245    const bool start_execution = execute_queue_.empty();
246    // If no setxkbmap command is in flight (i.e. start_execution is true),
247    // start the first one by explicitly calling MaybeExecuteSetLayoutCommand().
248    // If one or more setxkbmap commands are already in flight, just push the
249    // layout name to the queue. setxkbmap command for the layout will be called
250    // via OnSetLayoutFinish() callback later.
251    execute_queue_.push(layout_to_set);
252    if (start_execution) {
253      MaybeExecuteSetLayoutCommand();
254    }
255    return true;
256  }
257
258  // Executes 'setxkbmap -layout ...' command asynchronously using a layout name
259  // in the |execute_queue_|. Do nothing if the queue is empty.
260  // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
261  void MaybeExecuteSetLayoutCommand() {
262    if (execute_queue_.empty()) {
263      return;
264    }
265    const std::string layout_to_set = execute_queue_.front();
266
267    std::vector<std::string> argv;
268    base::file_handle_mapping_vector fds_to_remap;
269    base::ProcessHandle handle = base::kNullProcessHandle;
270
271    argv.push_back(kSetxkbmapCommand);
272    argv.push_back("-layout");
273    argv.push_back(layout_to_set);
274    argv.push_back("-synch");
275    const bool result = base::LaunchApp(argv,
276                                        fds_to_remap,  // No remapping.
277                                        false,  // Don't wait.
278                                        &handle);
279    if (!result) {
280      LOG(ERROR) << "Failed to execute setxkbmap: " << layout_to_set;
281      execute_queue_ = std::queue<std::string>();  // clear the queue.
282      return;
283    }
284
285    // g_child_watch_add is necessary to prevent the process from becoming a
286    // zombie.
287    const base::ProcessId pid = base::GetProcId(handle);
288    g_child_watch_add(pid,
289                      reinterpret_cast<GChildWatchFunc>(OnSetLayoutFinish),
290                      this);
291    VLOG(1) << "ExecuteSetLayoutCommand: " << layout_to_set << ": pid=" << pid;
292  }
293
294  static void OnSetLayoutFinish(GPid pid, gint status, XKeyboard* self) {
295    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
296    VLOG(1) << "OnSetLayoutFinish: pid=" << pid;
297    if (self->execute_queue_.empty()) {
298      LOG(ERROR) << "OnSetLayoutFinish: execute_queue_ is empty. "
299                 << "base::LaunchApp failed? pid=" << pid;
300      return;
301    }
302    self->execute_queue_.pop();
303    self->MaybeExecuteSetLayoutCommand();
304  }
305
306  // The XKB layout name which we set last time like "us" and "us(dvorak)".
307  std::string current_layout_name_;
308  // The mapping of modifier keys we set last time.
309  ModifierMap current_modifier_map_;
310  // A queue for executing setxkbmap one by one.
311  std::queue<std::string> execute_queue_;
312
313  DISALLOW_COPY_AND_ASSIGN(XKeyboard);
314};
315
316}  // namespace
317
318std::string CreateFullXkbLayoutName(const std::string& layout_name,
319                                    const ModifierMap& modifier_map) {
320  static const char kValidLayoutNameCharacters[] =
321      "abcdefghijklmnopqrstuvwxyz0123456789()-_";
322
323  if (layout_name.empty()) {
324    LOG(ERROR) << "Invalid layout_name: " << layout_name;
325    return "";
326  }
327
328  if (layout_name.find_first_not_of(kValidLayoutNameCharacters) !=
329      std::string::npos) {
330    LOG(ERROR) << "Invalid layout_name: " << layout_name;
331    return "";
332  }
333
334  std::string use_search_key_as_str;
335  std::string use_left_control_key_as_str;
336  std::string use_left_alt_key_as_str;
337
338  for (size_t i = 0; i < modifier_map.size(); ++i) {
339    std::string* target = NULL;
340    switch (modifier_map[i].original) {
341      case kSearchKey:
342        target = &use_search_key_as_str;
343        break;
344      case kLeftControlKey:
345        target = &use_left_control_key_as_str;
346        break;
347      case kLeftAltKey:
348        target = &use_left_alt_key_as_str;
349        break;
350      default:
351        break;
352    }
353    if (!target) {
354      LOG(ERROR) << "We don't support remaping "
355                 << ModifierKeyToString(modifier_map[i].original);
356      return "";
357    }
358    if (!(target->empty())) {
359      LOG(ERROR) << ModifierKeyToString(modifier_map[i].original)
360                 << " appeared twice";
361      return "";
362    }
363    *target = ModifierKeyToString(modifier_map[i].replacement);
364  }
365
366  if (use_search_key_as_str.empty() ||
367      use_left_control_key_as_str.empty() ||
368      use_left_alt_key_as_str.empty()) {
369    LOG(ERROR) << "Incomplete ModifierMap: size=" << modifier_map.size();
370    return "";
371  }
372
373  if (KeepCapsLock(layout_name)) {
374    use_search_key_as_str = ModifierKeyToString(kSearchKey);
375  }
376
377  std::string full_xkb_layout_name =
378      StringPrintf("%s+chromeos(%s_%s_%s%s)", layout_name.c_str(),
379                   use_search_key_as_str.c_str(),
380                   use_left_control_key_as_str.c_str(),
381                   use_left_alt_key_as_str.c_str(),
382                   KeepRightAlt(layout_name) ? "_keepralt" : "");
383
384  if ((full_xkb_layout_name.substr(0, 3) != "us+") &&
385      (full_xkb_layout_name.substr(0, 3) != "us(")) {
386    full_xkb_layout_name += ",us";
387  }
388
389  return full_xkb_layout_name;
390}
391
392// This function is only for unittest.
393bool CapsLockIsEnabled() {
394  ScopedDisplay display(XOpenDisplay(NULL));
395  if (!display.get()) {
396    return false;
397  }
398  XkbStateRec status;
399  XkbGetState(display.get(), XkbUseCoreKbd, &status);
400  return status.locked_mods & LockMask;
401}
402
403// TODO(yusukes): Call this function in non-UI thread or in an idle callback.
404void SetCapsLockEnabled(bool enable_caps_lock) {
405  ScopedDisplay display(XOpenDisplay(NULL));
406  if (!display.get()) {
407    return;
408  }
409  XkbLockModifiers(
410      display.get(), XkbUseCoreKbd, LockMask, enable_caps_lock ? LockMask : 0);
411}
412
413bool ContainsModifierKeyAsReplacement(
414    const ModifierMap& modifier_map, ModifierKey key) {
415  for (size_t i = 0; i < modifier_map.size(); ++i) {
416    if (modifier_map[i].replacement == key) {
417      return true;
418    }
419  }
420  return false;
421}
422
423bool SetCurrentKeyboardLayoutByName(const std::string& layout_name) {
424  return XKeyboard::GetInstance()->SetLayout(layout_name);
425}
426
427bool RemapModifierKeys(const ModifierMap& modifier_map) {
428  return XKeyboard::GetInstance()->RemapModifierKeys(modifier_map);
429}
430
431bool SetAutoRepeatEnabled(bool enabled) {
432  return XKeyboard::GetInstance()->SetAutoRepeatEnabled(enabled);
433}
434
435bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
436  return XKeyboard::GetInstance()->SetAutoRepeatRate(rate);
437}
438
439}  // namespace input_method
440}  // namespace chromeos
441