blob: 2b8014fa85420f50c7e82be97e16f2568b1f7985 [file] [log] [blame]
// Copyright (c) 2010 The Chromium OS 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 "chromeos_keyboard.h"
#include <utility>
#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include <glib.h>
#include <stdlib.h>
#include <string.h>
#include <xkeyboard_config_version.h>
#include "base/logging.h"
#include "base/singleton.h"
#include "base/string_util.h"
namespace {
// The command we use to set/get the current XKB layout and modifier key
// mapping.
const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
// See the comment at ModifierKey in the .h file.
chromeos::ModifierKey kCustomizableKeys[] = {
chromeos::kSearchKey,
chromeos::kLeftControlKey,
chromeos::kLeftAltKey
};
// 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* Get() {
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) {
// TODO(yusukes): write auto tests for the function.
chromeos::ModifierMap modifier_map;
if (!GetModifierMapping(&modifier_map)) {
LOG(ERROR) << "Failed to get modifier mapping.";
return false;
}
if (SetLayoutInternal(layout_name, modifier_map, true)) {
return true;
}
LOG(ERROR) << "SetLayoutInternal failed. Retrying without +version option";
return SetLayoutInternal(layout_name, modifier_map, false);
}
// Remaps modifier keys. This function does not change the current keyboard
// layout. Returns true on success.
bool RemapModifierKeys(const chromeos::ModifierMap& modifier_map) {
// TODO(yusukes): write auto tests for the function.
modifier_keys_are_remapped_ = false;
const std::string layout_name = GetLayout();
if (layout_name.empty()) {
return false;
}
if (SetLayoutInternal(layout_name, modifier_map, true)) {
modifier_keys_are_remapped_ = true;
return true;
}
LOG(ERROR) << "SetLayoutInternal failed. Retrying without +version option";
if (SetLayoutInternal(layout_name, modifier_map, false)) {
modifier_keys_are_remapped_ = true;
return true;
}
return false;
}
// Returns the current layout name like "us". On error, returns "".
std::string GetLayout() {
// TODO(yusukes): write auto tests for the function.
std::string command_output = last_full_layout_name_;
if (command_output.empty()) {
// Cache is not available. Execute setxkbmap to get the current layout.
if (!ExecuteGetLayoutCommand(&command_output)) {
return "";
}
}
const std::string layout_name =
chromeos::ExtractLayoutNameFromFullXkbLayoutName(command_output);
LOG(INFO) << "Current XKB layout name: " << layout_name;
return layout_name;
}
// Gets the current auto-repeat mode of the keyboard. The result is stored in
// |mode|. Returns true on success.
bool GetAutoRepeatEnabled(bool* enabled) {
// TODO(yusukes): write auto tests for the function.
DCHECK(enabled);
ScopedDisplay display(XOpenDisplay(NULL));
if (!display.get()) {
return false;
}
XKeyboardState values = {};
XGetKeyboardControl(display.get(), &values);
if (values.global_auto_repeat == 0) {
*enabled = false;
} else {
*enabled = true;
}
return true;
}
// Turns on and off the auto-repeat of the keyboard. Returns true on success.
bool SetAutoRepeatEnabled(bool enabled) {
// TODO(yusukes): write auto tests for the function.
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;
}
// Gets the current auto-repeat rate of the keyboard. The result is stored in
// |out_rate|. Returns true on success.
bool GetAutoRepeatRate(chromeos::AutoRepeatRate* out_rate) {
// TODO(yusukes): write auto tests for the function.
ScopedDisplay display(XOpenDisplay(NULL));
if (!display.get()) {
return false;
}
if (XkbGetAutoRepeatRate(display.get(), XkbUseCoreKbd,
&(out_rate->initial_delay_in_ms),
&(out_rate->repeat_interval_in_ms)) != True) {
out_rate->initial_delay_in_ms = 0;
out_rate->repeat_interval_in_ms = 0;
return false;
}
return true;
}
// Sets the auto-repeat rate of the keyboard, initial delay in ms, and repeat
// interval in ms. Returns true on success.
bool SetAutoRepeatRate(const chromeos::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() : modifier_keys_are_remapped_(false) {
InitializeStringToModifierMap(&string_to_modifier_map_);
}
~XKeyboard() {
}
// Gets the current modifier mapping and stores them on |out_modifier_map|.
bool GetModifierMapping(chromeos::ModifierMap* out_modifier_map) {
out_modifier_map->clear();
// If modifier keys are not remapped, return a map that doesn't change
// any key mappings.
if (!modifier_keys_are_remapped_) {
for (size_t i = 0; i < arraysize(kCustomizableKeys); ++i) {
chromeos::ModifierKey key = kCustomizableKeys[i];
out_modifier_map->push_back(chromeos::ModifierKeyPair(key, key));
}
return true;
}
std::string command_output = last_full_layout_name_;
if (command_output.empty()) {
// Cache is not available. Execute setxkbmap to get the current mapping.
if (!ExecuteGetLayoutCommand(&command_output)) {
return false;
}
}
if (!chromeos::ExtractModifierMapFromFullXkbLayoutName(
command_output, string_to_modifier_map_, out_modifier_map)) {
return false;
}
return true;
}
// This function is used by SetLayout() and RemapModifierKeys(). Calls
// setxkbmap command if needed, and updates the last_full_layout_name_ cache.
// If |use_version| is false, the function does not add "+version(...)" to the
// layout name. See http://crosbug.com/6261 for details.
bool SetLayoutInternal(const std::string& layout_name,
const chromeos::ModifierMap& modifier_map,
bool use_version) {
const std::string layouts_to_set = chromeos::CreateFullXkbLayoutName(
layout_name, modifier_map, use_version);
if (layouts_to_set.empty()) {
return false;
}
// Since executing setxkbmap takes more than 200 ms on EeePC, and this
// function is called on every focus-in event, try to reduce the number of
// the fork/exec calls.
if (last_full_layout_name_ == layouts_to_set) {
DLOG(INFO) << "The requested layout is already set: " << layouts_to_set;
return true;
}
// Turn off caps lock if there is no kCapsLockKey in the remapped keys.
if (!ContainsModifierKeyAsReplacement(
modifier_map, chromeos::kCapsLockKey)) {
chromeos::SetCapsLockEnabled(false);
}
gint exit_status = -1;
const gboolean successful =
ExecuteSetLayoutCommand(layouts_to_set, &exit_status);
// On success, update the cache and return true.
if (successful && (exit_status == 0)) {
last_full_layout_name_ = layouts_to_set;
DLOG(INFO) << "XKB layout is changed to " << layouts_to_set;
return true;
}
LOG(ERROR) << "Failed to change XKB layout to: " << layouts_to_set;
last_full_layout_name_.clear(); // invalidate the cache.
return false;
}
// Executes 'setxkbmap -layout ...' command. Returns true if execve suceeds.
bool ExecuteSetLayoutCommand(const std::string& layouts_to_set,
gint* out_exit_status) {
*out_exit_status = -1;
gchar* argv[] = {
g_strdup(kSetxkbmapCommand), g_strdup("-layout"),
g_strdup(layouts_to_set.c_str()), NULL
};
GError* error = NULL;
const gboolean successful = g_spawn_sync(NULL, argv, NULL,
static_cast<GSpawnFlags>(0),
NULL, NULL, NULL, NULL,
out_exit_status, &error);
for (size_t i = 0; argv[i] != NULL; ++i) {
g_free(argv[i]);
}
if (!successful) {
if (error && error->message) {
LOG(ERROR) << "Failed to execute setxkbmap: " << error->message;
}
g_error_free(error);
}
return (successful == TRUE);
}
// Executes 'setxkbmap -print' command and parses the output (stdout) from the
// command. Returns true if both execve and parsing the output succeed.
// On success, it stores a string like "us+chromeos(..)+version(..)+inet(..)"
// on |out_command_output|.
bool ExecuteGetLayoutCommand(std::string* out_command_output) {
out_command_output->clear();
gint exit_status = -1;
gchar* argv[] = { g_strdup(kSetxkbmapCommand), g_strdup("-print"), NULL };
gchar* raw_command_output = NULL;
GError* error = NULL;
const gboolean successful = g_spawn_sync(NULL, argv, NULL,
static_cast<GSpawnFlags>(0),
NULL, NULL,
&raw_command_output, NULL,
&exit_status, &error);
for (size_t i = 0; argv[i] != NULL; ++i) {
g_free(argv[i]);
}
if (!successful) {
if (error && error->message) {
LOG(ERROR) << "Failed to execute setxkbmap: " << error->message;
}
g_error_free(error);
return false;
}
// g_spawn_sync succeeded.
std::string command_output = raw_command_output ? raw_command_output : "";
g_free(raw_command_output);
raw_command_output = NULL; // DO NOT USE |raw_command_output| below.
if (exit_status != 0) {
return false;
}
// Parse a line like:
// "xkb_symbols { include "pc+us+chromeos(..)+version(..)+inet(pc105)" };"
const size_t cursor = command_output.find("pc+");
if (cursor == std::string::npos) {
LOG(ERROR) << "pc+ is not found: " << command_output;
return false;
}
*out_command_output = command_output.substr(cursor + 3); // Skip "pc+".
return true;
}
// The XKB layout name which we set last time like
// "us+chromeos(search_leftcontrol_leftalt)".
std::string last_full_layout_name_;
// A std::map that holds mappings like: "leftcontrol_disabled_leftalt" ->
// { LEFT_CONTROL_KEY, VOID_KEY, LEFT_ALT_KEY }.
chromeos::StringToModifierMap string_to_modifier_map_;
// True if modifier keys are remapped.
bool modifier_keys_are_remapped_;
DISALLOW_COPY_AND_ASSIGN(XKeyboard);
};
} // namespace
namespace chromeos {
std::string CreateFullXkbLayoutName(const std::string& layout_name,
const ModifierMap& modifier_map,
bool use_version) {
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 "";
}
std::string version;
if (use_version) {
version = std::string("+version(") + kXkeyboardConfigPackageVersion + ")";
}
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(),
version.c_str());
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;
}
std::string ExtractLayoutNameFromFullXkbLayoutName(
const std::string& full_xkb_layout_name) {
const size_t next_plus_pos = full_xkb_layout_name.find('+');
if (next_plus_pos == std::string::npos) {
LOG(ERROR) << "Bad layout name: " << full_xkb_layout_name;
return "";
}
return full_xkb_layout_name.substr(0, next_plus_pos);
}
void InitializeStringToModifierMap(StringToModifierMap* out_map) {
DCHECK(out_map);
out_map->clear();
for (int i = 0; i < static_cast<int>(kNumModifierKeys); ++i) {
for (int j = 0; j < static_cast<int>(kNumModifierKeys); ++j) {
for (int k = 0; k < static_cast<int>(kNumModifierKeys); ++k) {
const std::string string_rep = StringPrintf(
"%s_%s_%s",
ModifierKeyToString(ModifierKey(i)).c_str(),
ModifierKeyToString(ModifierKey(j)).c_str(),
ModifierKeyToString(ModifierKey(k)).c_str());
ModifierMap modifier_map;
modifier_map.push_back(ModifierKeyPair(kSearchKey, ModifierKey(i)));
modifier_map.push_back(
ModifierKeyPair(kLeftControlKey, ModifierKey(j)));
modifier_map.push_back(ModifierKeyPair(kLeftAltKey, ModifierKey(k)));
out_map->insert(make_pair(string_rep, modifier_map));
}
}
}
}
bool ExtractModifierMapFromFullXkbLayoutName(
const std::string& full_xkb_layout_name,
const StringToModifierMap& string_to_modifier_map,
ModifierMap* out_modifier_map) {
static const char kMark[] = "+chromeos(";
const size_t kMarkLen = strlen(kMark);
out_modifier_map->clear();
const size_t mark_pos = full_xkb_layout_name.find(kMark);
if (mark_pos == std::string::npos) {
LOG(ERROR) << "Bad layout name: " << full_xkb_layout_name;
return false;
}
const std::string tmp = // e.g. "leftcontrol_disabled_leftalt), us"
full_xkb_layout_name.substr(mark_pos + kMarkLen);
const size_t next_paren_pos = tmp.find(')');
if (next_paren_pos == std::string::npos) {
LOG(ERROR) << "Bad layout name: " << full_xkb_layout_name;
return false;
}
const std::string modifier_map_string = tmp.substr(0, next_paren_pos);
DLOG(INFO) << "Modifier mapping is: " << modifier_map_string;
StringToModifierMap::const_iterator iter =
string_to_modifier_map.find(modifier_map_string);
if (iter == string_to_modifier_map.end()) {
LOG(ERROR) << "Bad mapping name '" << modifier_map_string
<< "' in layout name '" << full_xkb_layout_name << "'";
return false;
}
*out_modifier_map = iter->second;
return true;
}
bool CapsLockIsEnabled() {
ScopedDisplay display(XOpenDisplay(NULL));
if (!display.get()) {
return false;
}
XkbStateRec status;
XkbGetState(display.get(), XkbUseCoreKbd, &status);
return status.locked_mods & LockMask;
}
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;
}
} // namespace chromeos
//
// licros APIs.
//
extern "C"
bool ChromeOSSetCurrentKeyboardLayoutByName(const std::string& layout_name) {
return XKeyboard::Get()->SetLayout(layout_name);
}
extern "C"
bool ChromeOSRemapModifierKeys(const chromeos::ModifierMap& modifier_map) {
return XKeyboard::Get()->RemapModifierKeys(modifier_map);
}
extern "C"
bool ChromeOSGetAutoRepeatEnabled(bool* enabled) {
return XKeyboard::Get()->GetAutoRepeatEnabled(enabled);
}
extern "C"
bool ChromeOSSetAutoRepeatEnabled(bool enabled) {
return XKeyboard::Get()->SetAutoRepeatEnabled(enabled);
}
extern "C"
bool ChromeOSGetAutoRepeatRate(chromeos::AutoRepeatRate* out_rate) {
return XKeyboard::Get()->GetAutoRepeatRate(out_rate);
}
extern "C"
bool ChromeOSSetAutoRepeatRate(const chromeos::AutoRepeatRate& rate) {
return XKeyboard::Get()->SetAutoRepeatRate(rate);
}