普通文本  |  441行  |  12.95 KB

// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/input_method/xkeyboard.h"

#include <queue>
#include <utility>

#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include <glib.h>
#include <stdlib.h>
#include <string.h>

#include "base/memory/singleton.h"
#include "base/logging.h"
#include "base/string_util.h"
#include "base/process_util.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/input_method/input_method_util.h"
#include "content/browser/browser_thread.h"

namespace chromeos {
namespace input_method {
namespace {

// The default keyboard layout name in the xorg config file.
const char kDefaultLayoutName[] = "us";
// The command we use to set the current XKB layout and modifier key mapping.
// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
// See the comment at ModifierKey in the .h file.
ModifierKey kCustomizableKeys[] = {
  kSearchKey,
  kLeftControlKey,
  kLeftAltKey
};

// These arrays are generated by 'gen_keyboard_overlay_data.py --altgr'
// These are the overlay names of layouts that shouldn't
// remap the right alt key.
const char* kKeepRightAltOverlays[] = {
  "el",
  "ca",
  "it",
  "iw",
  "es_419",
  "cs",
  "et",
  "es",
  "en_US_altgr_intl",
  "de_neo",
  "nl",
  "no",
  "tr",
  "lt",
  "pt_PT",
  "en_GB_dvorak",
  "fr",
  "bg",
  "pt_BR",
  "en_fr_hybrid_CA",
  "hr",
  "da",
  "fi",
  "fr_CA",
  "ko",
  "sv",
  "sk",
  "de",
  "en_GB",
  "pl",
  "uk",
  "sl",
  "en_US_intl",
};

// These are the overlay names with caps lock remapped
const char* kCapsLockRemapped[] = {
  "de_neo",
  "en_US_colemak",
};


bool KeepRightAlt(const std::string& layout_name) {
  for (size_t c = 0; c < arraysize(kKeepRightAltOverlays); ++c) {
    if (GetKeyboardOverlayId(layout_name) == kKeepRightAltOverlays[c]) {
      return true;
    }
  }
  return false;
}

bool KeepCapsLock(const std::string& layout_name) {
  for (size_t c = 0; c < arraysize(kCapsLockRemapped); ++c) {
    if (GetKeyboardOverlayId(layout_name) == kCapsLockRemapped[c]) {
      return true;
    }
  }
  return false;
}

// This is a wrapper class around Display, that opens and closes X display in
// the constructor and destructor.
class ScopedDisplay {
 public:
  explicit ScopedDisplay(Display* display) : display_(display) {
    if (!display_) {
      LOG(ERROR) << "NULL display_ is passed";
    }
  }

  ~ScopedDisplay() {
    if (display_) {
      XCloseDisplay(display_);
    }
  }

  Display* get() const {
    return display_;
  }

 private:
  Display* display_;

  DISALLOW_COPY_AND_ASSIGN(ScopedDisplay);
};

// A singleton class which wraps the setxkbmap command.
class XKeyboard {
 public:
  // Returns the singleton instance of the class. Use LeakySingletonTraits.
  // We don't delete the instance at exit.
  static XKeyboard* GetInstance() {
    return Singleton<XKeyboard, LeakySingletonTraits<XKeyboard> >::get();
  }

  // Sets the current keyboard layout to |layout_name|. This function does not
  // change the current mapping of the modifier keys. Returns true on success.
  bool SetLayout(const std::string& layout_name) {
    if (SetLayoutInternal(layout_name, current_modifier_map_)) {
      current_layout_name_ = layout_name;
      return true;
    }
    return false;
  }

  // Remaps modifier keys. This function does not change the current keyboard
  // layout. Returns true on success.
  bool RemapModifierKeys(const ModifierMap& modifier_map) {
    const std::string layout_name = current_layout_name_.empty() ?
        kDefaultLayoutName : current_layout_name_;
    if (SetLayoutInternal(layout_name, modifier_map)) {
      current_layout_name_ = layout_name;
      current_modifier_map_ = modifier_map;
      return true;
    }
    return false;
  }

  // Turns on and off the auto-repeat of the keyboard. Returns true on success.
  // TODO(yusukes): Remove this function.
  bool SetAutoRepeatEnabled(bool enabled) {
    ScopedDisplay display(XOpenDisplay(NULL));
    if (!display.get()) {
      return false;
    }
    if (enabled) {
      XAutoRepeatOn(display.get());
    } else {
      XAutoRepeatOff(display.get());
    }
    DLOG(INFO) << "Set auto-repeat mode to: " << (enabled ? "on" : "off");
    return true;
  }

  // Sets the auto-repeat rate of the keyboard, initial delay in ms, and repeat
  // interval in ms.  Returns true on success.
  // TODO(yusukes): Call this function in non-UI thread or in an idle callback.
  bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
    // TODO(yusukes): write auto tests for the function.
    ScopedDisplay display(XOpenDisplay(NULL));
    if (!display.get()) {
      return false;
    }

    DLOG(INFO) << "Set auto-repeat rate to: "
               << rate.initial_delay_in_ms << " ms delay, "
               << rate.repeat_interval_in_ms << " ms interval";
    if (XkbSetAutoRepeatRate(display.get(), XkbUseCoreKbd,
                             rate.initial_delay_in_ms,
                             rate.repeat_interval_in_ms) != True) {
      LOG(ERROR) << "Failed to set auto-repeat rate";
      return false;
    }
    return true;
  }

 private:
  friend struct DefaultSingletonTraits<XKeyboard>;

  XKeyboard() {
    for (size_t i = 0; i < arraysize(kCustomizableKeys); ++i) {
      ModifierKey key = kCustomizableKeys[i];
      current_modifier_map_.push_back(ModifierKeyPair(key, key));
    }
  }
  ~XKeyboard() {
  }

  // This function is used by SetLayout() and RemapModifierKeys(). Calls
  // setxkbmap command if needed, and updates the last_full_layout_name_ cache.
  bool SetLayoutInternal(const std::string& layout_name,
                         const ModifierMap& modifier_map) {
    if (!CrosLibrary::Get()->EnsureLoaded()) {
      // We should not try to change a layout inside ui_tests.
      return false;
    }

    const std::string layout_to_set = CreateFullXkbLayoutName(
        layout_name, modifier_map);
    if (layout_to_set.empty()) {
      return false;
    }

    if (!current_layout_name_.empty()) {
      const std::string current_layout = CreateFullXkbLayoutName(
          current_layout_name_, current_modifier_map_);
      if (current_layout == layout_to_set) {
        DLOG(INFO) << "The requested layout is already set: " << layout_to_set;
        return true;
      }
    }

    // Turn off caps lock if there is no kCapsLockKey in the remapped keys.
    if (!ContainsModifierKeyAsReplacement(
            modifier_map, kCapsLockKey)) {
      SetCapsLockEnabled(false);
    }

    // TODO(yusukes): Revert to VLOG(1) when crosbug.com/15851 is resolved.
    LOG(WARNING) << "Set layout: " << layout_to_set;

    const bool start_execution = execute_queue_.empty();
    // If no setxkbmap command is in flight (i.e. start_execution is true),
    // start the first one by explicitly calling MaybeExecuteSetLayoutCommand().
    // If one or more setxkbmap commands are already in flight, just push the
    // layout name to the queue. setxkbmap command for the layout will be called
    // via OnSetLayoutFinish() callback later.
    execute_queue_.push(layout_to_set);
    if (start_execution) {
      MaybeExecuteSetLayoutCommand();
    }
    return true;
  }

  // Executes 'setxkbmap -layout ...' command asynchronously using a layout name
  // in the |execute_queue_|. Do nothing if the queue is empty.
  // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
  void MaybeExecuteSetLayoutCommand() {
    if (execute_queue_.empty()) {
      return;
    }
    const std::string layout_to_set = execute_queue_.front();

    std::vector<std::string> argv;
    base::file_handle_mapping_vector fds_to_remap;
    base::ProcessHandle handle = base::kNullProcessHandle;

    argv.push_back(kSetxkbmapCommand);
    argv.push_back("-layout");
    argv.push_back(layout_to_set);
    argv.push_back("-synch");
    const bool result = base::LaunchApp(argv,
                                        fds_to_remap,  // No remapping.
                                        false,  // Don't wait.
                                        &handle);
    if (!result) {
      LOG(ERROR) << "Failed to execute setxkbmap: " << layout_to_set;
      execute_queue_ = std::queue<std::string>();  // clear the queue.
      return;
    }

    // g_child_watch_add is necessary to prevent the process from becoming a
    // zombie.
    const base::ProcessId pid = base::GetProcId(handle);
    g_child_watch_add(pid,
                      reinterpret_cast<GChildWatchFunc>(OnSetLayoutFinish),
                      this);
    VLOG(1) << "ExecuteSetLayoutCommand: " << layout_to_set << ": pid=" << pid;
  }

  static void OnSetLayoutFinish(GPid pid, gint status, XKeyboard* self) {
    CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    VLOG(1) << "OnSetLayoutFinish: pid=" << pid;
    if (self->execute_queue_.empty()) {
      LOG(ERROR) << "OnSetLayoutFinish: execute_queue_ is empty. "
                 << "base::LaunchApp failed? pid=" << pid;
      return;
    }
    self->execute_queue_.pop();
    self->MaybeExecuteSetLayoutCommand();
  }

  // The XKB layout name which we set last time like "us" and "us(dvorak)".
  std::string current_layout_name_;
  // The mapping of modifier keys we set last time.
  ModifierMap current_modifier_map_;
  // A queue for executing setxkbmap one by one.
  std::queue<std::string> execute_queue_;

  DISALLOW_COPY_AND_ASSIGN(XKeyboard);
};

}  // namespace

std::string CreateFullXkbLayoutName(const std::string& layout_name,
                                    const ModifierMap& modifier_map) {
  static const char kValidLayoutNameCharacters[] =
      "abcdefghijklmnopqrstuvwxyz0123456789()-_";

  if (layout_name.empty()) {
    LOG(ERROR) << "Invalid layout_name: " << layout_name;
    return "";
  }

  if (layout_name.find_first_not_of(kValidLayoutNameCharacters) !=
      std::string::npos) {
    LOG(ERROR) << "Invalid layout_name: " << layout_name;
    return "";
  }

  std::string use_search_key_as_str;
  std::string use_left_control_key_as_str;
  std::string use_left_alt_key_as_str;

  for (size_t i = 0; i < modifier_map.size(); ++i) {
    std::string* target = NULL;
    switch (modifier_map[i].original) {
      case kSearchKey:
        target = &use_search_key_as_str;
        break;
      case kLeftControlKey:
        target = &use_left_control_key_as_str;
        break;
      case kLeftAltKey:
        target = &use_left_alt_key_as_str;
        break;
      default:
        break;
    }
    if (!target) {
      LOG(ERROR) << "We don't support remaping "
                 << ModifierKeyToString(modifier_map[i].original);
      return "";
    }
    if (!(target->empty())) {
      LOG(ERROR) << ModifierKeyToString(modifier_map[i].original)
                 << " appeared twice";
      return "";
    }
    *target = ModifierKeyToString(modifier_map[i].replacement);
  }

  if (use_search_key_as_str.empty() ||
      use_left_control_key_as_str.empty() ||
      use_left_alt_key_as_str.empty()) {
    LOG(ERROR) << "Incomplete ModifierMap: size=" << modifier_map.size();
    return "";
  }

  if (KeepCapsLock(layout_name)) {
    use_search_key_as_str = ModifierKeyToString(kSearchKey);
  }

  std::string full_xkb_layout_name =
      StringPrintf("%s+chromeos(%s_%s_%s%s)", layout_name.c_str(),
                   use_search_key_as_str.c_str(),
                   use_left_control_key_as_str.c_str(),
                   use_left_alt_key_as_str.c_str(),
                   KeepRightAlt(layout_name) ? "_keepralt" : "");

  if ((full_xkb_layout_name.substr(0, 3) != "us+") &&
      (full_xkb_layout_name.substr(0, 3) != "us(")) {
    full_xkb_layout_name += ",us";
  }

  return full_xkb_layout_name;
}

// This function is only for unittest.
bool CapsLockIsEnabled() {
  ScopedDisplay display(XOpenDisplay(NULL));
  if (!display.get()) {
    return false;
  }
  XkbStateRec status;
  XkbGetState(display.get(), XkbUseCoreKbd, &status);
  return status.locked_mods & LockMask;
}

// TODO(yusukes): Call this function in non-UI thread or in an idle callback.
void SetCapsLockEnabled(bool enable_caps_lock) {
  ScopedDisplay display(XOpenDisplay(NULL));
  if (!display.get()) {
    return;
  }
  XkbLockModifiers(
      display.get(), XkbUseCoreKbd, LockMask, enable_caps_lock ? LockMask : 0);
}

bool ContainsModifierKeyAsReplacement(
    const ModifierMap& modifier_map, ModifierKey key) {
  for (size_t i = 0; i < modifier_map.size(); ++i) {
    if (modifier_map[i].replacement == key) {
      return true;
    }
  }
  return false;
}

bool SetCurrentKeyboardLayoutByName(const std::string& layout_name) {
  return XKeyboard::GetInstance()->SetLayout(layout_name);
}

bool RemapModifierKeys(const ModifierMap& modifier_map) {
  return XKeyboard::GetInstance()->RemapModifierKeys(modifier_map);
}

bool SetAutoRepeatEnabled(bool enabled) {
  return XKeyboard::GetInstance()->SetAutoRepeatEnabled(enabled);
}

bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
  return XKeyboard::GetInstance()->SetAutoRepeatRate(rate);
}

}  // namespace input_method
}  // namespace chromeos