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