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