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