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
24 namespace chromeos {
25 namespace input_method {
26 namespace {
27
28 // The default keyboard layout name in the xorg config file.
29 const 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)
32 const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
33 // See the comment at ModifierKey in the .h file.
34 ModifierKey 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.
43 const 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
80 const char* kCapsLockRemapped[] = {
81 "de_neo",
82 "en_US_colemak",
83 };
84
85
KeepRightAlt(const std::string & layout_name)86 bool 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
KeepCapsLock(const std::string & layout_name)95 bool 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.
106 class ScopedDisplay {
107 public:
ScopedDisplay(Display * display)108 explicit ScopedDisplay(Display* display) : display_(display) {
109 if (!display_) {
110 LOG(ERROR) << "NULL display_ is passed";
111 }
112 }
113
~ScopedDisplay()114 ~ScopedDisplay() {
115 if (display_) {
116 XCloseDisplay(display_);
117 }
118 }
119
get() const120 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.
131 class XKeyboard {
132 public:
133 // Returns the singleton instance of the class. Use LeakySingletonTraits.
134 // We don't delete the instance at exit.
GetInstance()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.
SetLayout(const std::string & layout_name)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.
RemapModifierKeys(const ModifierMap & modifier_map)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.
SetAutoRepeatEnabled(bool enabled)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.
SetAutoRepeatRate(const AutoRepeatRate & rate)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
XKeyboard()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 }
~XKeyboard()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.
SetLayoutInternal(const std::string & layout_name,const ModifierMap & modifier_map)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)
MaybeExecuteSetLayoutCommand()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
OnSetLayoutFinish(GPid pid,gint status,XKeyboard * self)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
CreateFullXkbLayoutName(const std::string & layout_name,const ModifierMap & modifier_map)318 std::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.
CapsLockIsEnabled()393 bool 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.
SetCapsLockEnabled(bool enable_caps_lock)404 void 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
ContainsModifierKeyAsReplacement(const ModifierMap & modifier_map,ModifierKey key)413 bool 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
SetCurrentKeyboardLayoutByName(const std::string & layout_name)423 bool SetCurrentKeyboardLayoutByName(const std::string& layout_name) {
424 return XKeyboard::GetInstance()->SetLayout(layout_name);
425 }
426
RemapModifierKeys(const ModifierMap & modifier_map)427 bool RemapModifierKeys(const ModifierMap& modifier_map) {
428 return XKeyboard::GetInstance()->RemapModifierKeys(modifier_map);
429 }
430
SetAutoRepeatEnabled(bool enabled)431 bool SetAutoRepeatEnabled(bool enabled) {
432 return XKeyboard::GetInstance()->SetAutoRepeatEnabled(enabled);
433 }
434
SetAutoRepeatRate(const AutoRepeatRate & rate)435 bool SetAutoRepeatRate(const AutoRepeatRate& rate) {
436 return XKeyboard::GetInstance()->SetAutoRepeatRate(rate);
437 }
438
439 } // namespace input_method
440 } // namespace chromeos
441