xkeyboard.cc revision 4e180b6a0b4720a9b8e9e959a882386f690f08ff
1// Copyright 2013 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 "chromeos/ime/xkeyboard.h" 6 7#include <cstdlib> 8#include <cstring> 9#include <queue> 10#include <set> 11#include <utility> 12 13#include "base/logging.h" 14#include "base/memory/scoped_ptr.h" 15#include "base/message_loop/message_loop.h" 16#include "base/process/launch.h" 17#include "base/process/process_handle.h" 18#include "base/strings/string_util.h" 19#include "base/strings/stringprintf.h" 20#include "base/sys_info.h" 21#include "base/threading/thread_checker.h" 22 23// These includes conflict with base/tracked_objects.h so must come last. 24#include <X11/XKBlib.h> 25#include <X11/Xlib.h> 26#include <glib.h> 27 28namespace chromeos { 29namespace input_method { 30namespace { 31 32Display* GetXDisplay() { 33 return base::MessagePumpForUI::GetDefaultXDisplay(); 34} 35 36// The default keyboard layout name in the xorg config file. 37const char kDefaultLayoutName[] = "us"; 38 39// The command we use to set the current XKB layout and modifier key mapping. 40// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105) 41const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap"; 42 43// A string for obtaining a mask value for Num Lock. 44const char kNumLockVirtualModifierString[] = "NumLock"; 45 46// Returns false if |layout_name| contains a bad character. 47bool CheckLayoutName(const std::string& layout_name) { 48 static const char kValidLayoutNameCharacters[] = 49 "abcdefghijklmnopqrstuvwxyz0123456789()-_"; 50 51 if (layout_name.empty()) { 52 DVLOG(1) << "Invalid layout_name: " << layout_name; 53 return false; 54 } 55 56 if (layout_name.find_first_not_of(kValidLayoutNameCharacters) != 57 std::string::npos) { 58 DVLOG(1) << "Invalid layout_name: " << layout_name; 59 return false; 60 } 61 62 return true; 63} 64 65class XKeyboardImpl : public XKeyboard { 66 public: 67 XKeyboardImpl(); 68 virtual ~XKeyboardImpl() {} 69 70 // Overridden from XKeyboard: 71 virtual bool SetCurrentKeyboardLayoutByName( 72 const std::string& layout_name) OVERRIDE; 73 virtual bool ReapplyCurrentKeyboardLayout() OVERRIDE; 74 virtual void ReapplyCurrentModifierLockStatus() OVERRIDE; 75 virtual void SetLockedModifiers( 76 ModifierLockStatus new_caps_lock_status, 77 ModifierLockStatus new_num_lock_status) OVERRIDE; 78 virtual void SetNumLockEnabled(bool enable_num_lock) OVERRIDE; 79 virtual void SetCapsLockEnabled(bool enable_caps_lock) OVERRIDE; 80 virtual bool NumLockIsEnabled() OVERRIDE; 81 virtual bool CapsLockIsEnabled() OVERRIDE; 82 virtual unsigned int GetNumLockMask() OVERRIDE; 83 virtual void GetLockedModifiers(bool* out_caps_lock_enabled, 84 bool* out_num_lock_enabled) OVERRIDE; 85 86 private: 87 // This function is used by SetLayout() and RemapModifierKeys(). Calls 88 // setxkbmap command if needed, and updates the last_full_layout_name_ cache. 89 bool SetLayoutInternal(const std::string& layout_name, bool force); 90 91 // Executes 'setxkbmap -layout ...' command asynchronously using a layout name 92 // in the |execute_queue_|. Do nothing if the queue is empty. 93 // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105) 94 void MaybeExecuteSetLayoutCommand(); 95 96 // Called when execve'd setxkbmap process exits. 97 static void OnSetLayoutFinish(pid_t pid, int status, XKeyboardImpl* self); 98 99 const bool is_running_on_chrome_os_; 100 unsigned int num_lock_mask_; 101 102 // The current Num Lock and Caps Lock status. If true, enabled. 103 bool current_num_lock_status_; 104 bool current_caps_lock_status_; 105 // The XKB layout name which we set last time like "us" and "us(dvorak)". 106 std::string current_layout_name_; 107 108 // A queue for executing setxkbmap one by one. 109 std::queue<std::string> execute_queue_; 110 111 base::ThreadChecker thread_checker_; 112 113 DISALLOW_COPY_AND_ASSIGN(XKeyboardImpl); 114}; 115 116XKeyboardImpl::XKeyboardImpl() 117 : is_running_on_chrome_os_(base::SysInfo::IsRunningOnChromeOS()) { 118 num_lock_mask_ = GetNumLockMask(); 119 120 // web_input_event_aurax11.cc seems to assume that Mod2Mask is always assigned 121 // to Num Lock. 122 // TODO(yusukes): Check the assumption is really okay. If not, modify the Aura 123 // code, and then remove the CHECK below. 124 CHECK(!is_running_on_chrome_os_ || (num_lock_mask_ == Mod2Mask)); 125 GetLockedModifiers(¤t_caps_lock_status_, ¤t_num_lock_status_); 126} 127 128bool XKeyboardImpl::SetLayoutInternal(const std::string& layout_name, 129 bool force) { 130 if (!is_running_on_chrome_os_) { 131 // We should not try to change a layout on Linux or inside ui_tests. Just 132 // return true. 133 return true; 134 } 135 136 if (!CheckLayoutName(layout_name)) 137 return false; 138 139 if (!force && (current_layout_name_ == layout_name)) { 140 DVLOG(1) << "The requested layout is already set: " << layout_name; 141 return true; 142 } 143 144 DVLOG(1) << (force ? "Reapply" : "Set") << " layout: " << layout_name; 145 146 const bool start_execution = execute_queue_.empty(); 147 // If no setxkbmap command is in flight (i.e. start_execution is true), 148 // start the first one by explicitly calling MaybeExecuteSetLayoutCommand(). 149 // If one or more setxkbmap commands are already in flight, just push the 150 // layout name to the queue. setxkbmap command for the layout will be called 151 // via OnSetLayoutFinish() callback later. 152 execute_queue_.push(layout_name); 153 if (start_execution) 154 MaybeExecuteSetLayoutCommand(); 155 156 return true; 157} 158 159// Executes 'setxkbmap -layout ...' command asynchronously using a layout name 160// in the |execute_queue_|. Do nothing if the queue is empty. 161// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105) 162void XKeyboardImpl::MaybeExecuteSetLayoutCommand() { 163 if (execute_queue_.empty()) 164 return; 165 const std::string layout_to_set = execute_queue_.front(); 166 167 std::vector<std::string> argv; 168 base::ProcessHandle handle = base::kNullProcessHandle; 169 170 argv.push_back(kSetxkbmapCommand); 171 argv.push_back("-layout"); 172 argv.push_back(layout_to_set); 173 argv.push_back("-synch"); 174 175 if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) { 176 DVLOG(1) << "Failed to execute setxkbmap: " << layout_to_set; 177 execute_queue_ = std::queue<std::string>(); // clear the queue. 178 return; 179 } 180 181 // g_child_watch_add is necessary to prevent the process from becoming a 182 // zombie. 183 const base::ProcessId pid = base::GetProcId(handle); 184 g_child_watch_add(pid, 185 reinterpret_cast<GChildWatchFunc>(OnSetLayoutFinish), 186 this); 187 DVLOG(1) << "ExecuteSetLayoutCommand: " << layout_to_set << ": pid=" << pid; 188} 189 190bool XKeyboardImpl::NumLockIsEnabled() { 191 bool num_lock_enabled = false; 192 GetLockedModifiers(NULL /* Caps Lock */, &num_lock_enabled); 193 return num_lock_enabled; 194} 195 196bool XKeyboardImpl::CapsLockIsEnabled() { 197 bool caps_lock_enabled = false; 198 GetLockedModifiers(&caps_lock_enabled, NULL /* Num Lock */); 199 return caps_lock_enabled; 200} 201 202unsigned int XKeyboardImpl::GetNumLockMask() { 203 DCHECK(thread_checker_.CalledOnValidThread()); 204 static const unsigned int kBadMask = 0; 205 206 unsigned int real_mask = kBadMask; 207 XkbDescPtr xkb_desc = 208 XkbGetKeyboard(GetXDisplay(), XkbAllComponentsMask, XkbUseCoreKbd); 209 if (!xkb_desc) 210 return kBadMask; 211 212 if (xkb_desc->dpy && xkb_desc->names && xkb_desc->names->vmods) { 213 const std::string string_to_find(kNumLockVirtualModifierString); 214 for (size_t i = 0; i < XkbNumVirtualMods; ++i) { 215 const unsigned int virtual_mod_mask = 1U << i; 216 char* virtual_mod_str_raw_ptr = 217 XGetAtomName(xkb_desc->dpy, xkb_desc->names->vmods[i]); 218 if (!virtual_mod_str_raw_ptr) 219 continue; 220 const std::string virtual_mod_str = virtual_mod_str_raw_ptr; 221 XFree(virtual_mod_str_raw_ptr); 222 223 if (string_to_find == virtual_mod_str) { 224 if (!XkbVirtualModsToReal(xkb_desc, virtual_mod_mask, &real_mask)) { 225 DVLOG(1) << "XkbVirtualModsToReal failed"; 226 real_mask = kBadMask; // reset the return value, just in case. 227 } 228 break; 229 } 230 } 231 } 232 XkbFreeKeyboard(xkb_desc, 0, True /* free all components */); 233 return real_mask; 234} 235 236void XKeyboardImpl::GetLockedModifiers(bool* out_caps_lock_enabled, 237 bool* out_num_lock_enabled) { 238 DCHECK(thread_checker_.CalledOnValidThread()); 239 240 if (out_num_lock_enabled && !num_lock_mask_) { 241 DVLOG(1) << "Cannot get locked modifiers. Num Lock mask unknown."; 242 if (out_caps_lock_enabled) 243 *out_caps_lock_enabled = false; 244 if (out_num_lock_enabled) 245 *out_num_lock_enabled = false; 246 return; 247 } 248 249 XkbStateRec status; 250 XkbGetState(GetXDisplay(), XkbUseCoreKbd, &status); 251 if (out_caps_lock_enabled) 252 *out_caps_lock_enabled = status.locked_mods & LockMask; 253 if (out_num_lock_enabled) 254 *out_num_lock_enabled = status.locked_mods & num_lock_mask_; 255} 256 257void XKeyboardImpl::SetLockedModifiers(ModifierLockStatus new_caps_lock_status, 258 ModifierLockStatus new_num_lock_status) { 259 DCHECK(thread_checker_.CalledOnValidThread()); 260 if (!num_lock_mask_) { 261 DVLOG(1) << "Cannot set locked modifiers. Num Lock mask unknown."; 262 return; 263 } 264 265 unsigned int affect_mask = 0; 266 unsigned int value_mask = 0; 267 if (new_caps_lock_status != kDontChange) { 268 affect_mask |= LockMask; 269 value_mask |= ((new_caps_lock_status == kEnableLock) ? LockMask : 0); 270 current_caps_lock_status_ = (new_caps_lock_status == kEnableLock); 271 } 272 if (new_num_lock_status != kDontChange) { 273 affect_mask |= num_lock_mask_; 274 value_mask |= ((new_num_lock_status == kEnableLock) ? num_lock_mask_ : 0); 275 current_num_lock_status_ = (new_num_lock_status == kEnableLock); 276 } 277 278 if (affect_mask) 279 XkbLockModifiers(GetXDisplay(), XkbUseCoreKbd, affect_mask, value_mask); 280} 281 282void XKeyboardImpl::SetNumLockEnabled(bool enable_num_lock) { 283 SetLockedModifiers( 284 kDontChange, enable_num_lock ? kEnableLock : kDisableLock); 285} 286 287void XKeyboardImpl::SetCapsLockEnabled(bool enable_caps_lock) { 288 SetLockedModifiers( 289 enable_caps_lock ? kEnableLock : kDisableLock, kDontChange); 290} 291 292bool XKeyboardImpl::SetCurrentKeyboardLayoutByName( 293 const std::string& layout_name) { 294 if (SetLayoutInternal(layout_name, false)) { 295 current_layout_name_ = layout_name; 296 return true; 297 } 298 return false; 299} 300 301bool XKeyboardImpl::ReapplyCurrentKeyboardLayout() { 302 if (current_layout_name_.empty()) { 303 DVLOG(1) << "Can't reapply XKB layout: layout unknown"; 304 return false; 305 } 306 return SetLayoutInternal(current_layout_name_, true /* force */); 307} 308 309void XKeyboardImpl::ReapplyCurrentModifierLockStatus() { 310 SetLockedModifiers(current_caps_lock_status_ ? kEnableLock : kDisableLock, 311 current_num_lock_status_ ? kEnableLock : kDisableLock); 312} 313 314// static 315void XKeyboardImpl::OnSetLayoutFinish(pid_t pid, 316 int status, 317 XKeyboardImpl* self) { 318 DVLOG(1) << "OnSetLayoutFinish: pid=" << pid; 319 if (self->execute_queue_.empty()) { 320 DVLOG(1) << "OnSetLayoutFinish: execute_queue_ is empty. " 321 << "base::LaunchProcess failed? pid=" << pid; 322 return; 323 } 324 self->execute_queue_.pop(); 325 self->MaybeExecuteSetLayoutCommand(); 326} 327 328} // namespace 329 330// static 331bool XKeyboard::SetAutoRepeatEnabled(bool enabled) { 332 if (enabled) 333 XAutoRepeatOn(GetXDisplay()); 334 else 335 XAutoRepeatOff(GetXDisplay()); 336 DVLOG(1) << "Set auto-repeat mode to: " << (enabled ? "on" : "off"); 337 return true; 338} 339 340// static 341bool XKeyboard::SetAutoRepeatRate(const AutoRepeatRate& rate) { 342 DVLOG(1) << "Set auto-repeat rate to: " 343 << rate.initial_delay_in_ms << " ms delay, " 344 << rate.repeat_interval_in_ms << " ms interval"; 345 if (XkbSetAutoRepeatRate(GetXDisplay(), XkbUseCoreKbd, 346 rate.initial_delay_in_ms, 347 rate.repeat_interval_in_ms) != True) { 348 DVLOG(1) << "Failed to set auto-repeat rate"; 349 return false; 350 } 351 return true; 352} 353 354// static 355bool XKeyboard::GetAutoRepeatEnabledForTesting() { 356 XKeyboardState state = {}; 357 XGetKeyboardControl(GetXDisplay(), &state); 358 return state.global_auto_repeat != AutoRepeatModeOff; 359} 360 361// static 362bool XKeyboard::GetAutoRepeatRateForTesting(AutoRepeatRate* out_rate) { 363 return XkbGetAutoRepeatRate(GetXDisplay(), XkbUseCoreKbd, 364 &(out_rate->initial_delay_in_ms), 365 &(out_rate->repeat_interval_in_ms)) == True; 366} 367 368// static 369bool XKeyboard::CheckLayoutNameForTesting(const std::string& layout_name) { 370 return CheckLayoutName(layout_name); 371} 372 373// static 374XKeyboard* XKeyboard::Create() { 375 return new XKeyboardImpl(); 376} 377 378} // namespace input_method 379} // namespace chromeos 380