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