Home | History | Annotate | Download | only in input_method
      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 
     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 
     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:
    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.
    131 class 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 
    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.
    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.
    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 
    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 
    423 bool SetCurrentKeyboardLayoutByName(const std::string& layout_name) {
    424   return XKeyboard::GetInstance()->SetLayout(layout_name);
    425 }
    426 
    427 bool RemapModifierKeys(const ModifierMap& modifier_map) {
    428   return XKeyboard::GetInstance()->RemapModifierKeys(modifier_map);
    429 }
    430 
    431 bool SetAutoRepeatEnabled(bool enabled) {
    432   return XKeyboard::GetInstance()->SetAutoRepeatEnabled(enabled);
    433 }
    434 
    435 bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
    436   return XKeyboard::GetInstance()->SetAutoRepeatRate(rate);
    437 }
    438 
    439 }  // namespace input_method
    440 }  // namespace chromeos
    441