• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
24 namespace chromeos {
25 namespace input_method {
26 namespace {
27 
28 // The default keyboard layout name in the xorg config file.
29 const 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)
32 const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
33 // See the comment at ModifierKey in the .h file.
34 ModifierKey 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.
43 const 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
80 const char* kCapsLockRemapped[] = {
81   "de_neo",
82   "en_US_colemak",
83 };
84 
85 
KeepRightAlt(const std::string & layout_name)86 bool 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 
KeepCapsLock(const std::string & layout_name)95 bool 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.
106 class ScopedDisplay {
107  public:
ScopedDisplay(Display * display)108   explicit ScopedDisplay(Display* display) : display_(display) {
109     if (!display_) {
110       LOG(ERROR) << "NULL display_ is passed";
111     }
112   }
113 
~ScopedDisplay()114   ~ScopedDisplay() {
115     if (display_) {
116       XCloseDisplay(display_);
117     }
118   }
119 
get() const120   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.
131 class XKeyboard {
132  public:
133   // Returns the singleton instance of the class. Use LeakySingletonTraits.
134   // We don't delete the instance at exit.
GetInstance()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.
SetLayout(const std::string & layout_name)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.
RemapModifierKeys(const ModifierMap & modifier_map)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.
SetAutoRepeatEnabled(bool enabled)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.
SetAutoRepeatRate(const AutoRepeatRate & rate)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 
XKeyboard()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   }
~XKeyboard()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.
SetLayoutInternal(const std::string & layout_name,const ModifierMap & modifier_map)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)
MaybeExecuteSetLayoutCommand()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 
OnSetLayoutFinish(GPid pid,gint status,XKeyboard * self)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 
CreateFullXkbLayoutName(const std::string & layout_name,const ModifierMap & modifier_map)318 std::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.
CapsLockIsEnabled()393 bool 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.
SetCapsLockEnabled(bool enable_caps_lock)404 void 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 
ContainsModifierKeyAsReplacement(const ModifierMap & modifier_map,ModifierKey key)413 bool 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 
SetCurrentKeyboardLayoutByName(const std::string & layout_name)423 bool SetCurrentKeyboardLayoutByName(const std::string& layout_name) {
424   return XKeyboard::GetInstance()->SetLayout(layout_name);
425 }
426 
RemapModifierKeys(const ModifierMap & modifier_map)427 bool RemapModifierKeys(const ModifierMap& modifier_map) {
428   return XKeyboard::GetInstance()->RemapModifierKeys(modifier_map);
429 }
430 
SetAutoRepeatEnabled(bool enabled)431 bool SetAutoRepeatEnabled(bool enabled) {
432   return XKeyboard::GetInstance()->SetAutoRepeatEnabled(enabled);
433 }
434 
SetAutoRepeatRate(const AutoRepeatRate & rate)435 bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
436   return XKeyboard::GetInstance()->SetAutoRepeatRate(rate);
437 }
438 
439 }  // namespace input_method
440 }  // namespace chromeos
441