1 // Copyright 2014 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.
5 #include "ui/base/ime/chromeos/ime_keyboard_x11.h"
7 #include <X11/XKBlib.h>
10 #include "ui/gfx/x/x11_types.h"
13 namespace input_method
{
16 // The delay in milliseconds that we'll wait between checking if
17 // setxkbmap command finished.
18 const int kSetLayoutCommandCheckDelayMs
= 100;
20 // The command we use to set the current XKB layout and modifier key mapping.
21 // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
22 const char kSetxkbmapCommand
[] = "/usr/bin/setxkbmap";
24 // A string for obtaining a mask value for Num Lock.
25 const char kNumLockVirtualModifierString
[] = "NumLock";
27 // Returns false if |layout_name| contains a bad character.
28 bool CheckLayoutName(const std::string
& layout_name
) {
29 static const char kValidLayoutNameCharacters
[] =
30 "abcdefghijklmnopqrstuvwxyz0123456789()-_";
32 if (layout_name
.empty()) {
33 DVLOG(1) << "Invalid layout_name: " << layout_name
;
37 if (layout_name
.find_first_not_of(kValidLayoutNameCharacters
) !=
39 DVLOG(1) << "Invalid layout_name: " << layout_name
;
48 ImeKeyboardX11::ImeKeyboardX11()
49 : is_running_on_chrome_os_(base::SysInfo::IsRunningOnChromeOS()),
51 // X must be already initialized.
52 CHECK(gfx::GetXDisplay());
54 num_lock_mask_
= GetNumLockMask();
56 if (is_running_on_chrome_os_
) {
57 // Some code seems to assume that Mod2Mask is always assigned to
60 // TODO(yusukes): Check the assumption is really okay. If not,
61 // modify the Aura code, and then remove the CHECK below.
62 LOG_IF(ERROR
, num_lock_mask_
!= Mod2Mask
)
63 << "NumLock is not assigned to Mod2Mask. : " << num_lock_mask_
;
66 caps_lock_is_enabled_
= CapsLockIsEnabled();
67 // Disable Num Lock on X start up for http://crosbug.com/29169.
71 ImeKeyboardX11::~ImeKeyboardX11() {}
73 unsigned int ImeKeyboardX11::GetNumLockMask() {
74 DCHECK(thread_checker_
.CalledOnValidThread());
75 static const unsigned int kBadMask
= 0;
77 unsigned int real_mask
= kBadMask
;
79 XkbGetKeyboard(gfx::GetXDisplay(), XkbAllComponentsMask
, XkbUseCoreKbd
);
83 if (xkb_desc
->dpy
&& xkb_desc
->names
) {
84 const std::string
string_to_find(kNumLockVirtualModifierString
);
85 for (size_t i
= 0; i
< XkbNumVirtualMods
; ++i
) {
86 const unsigned int virtual_mod_mask
= 1U << i
;
87 gfx::XScopedPtr
<char> virtual_mod_str_raw_ptr(
88 XGetAtomName(xkb_desc
->dpy
, xkb_desc
->names
->vmods
[i
]));
89 if (!virtual_mod_str_raw_ptr
)
91 const std::string virtual_mod_str
= virtual_mod_str_raw_ptr
.get();
93 if (string_to_find
== virtual_mod_str
) {
94 if (!XkbVirtualModsToReal(xkb_desc
, virtual_mod_mask
, &real_mask
)) {
95 DVLOG(1) << "XkbVirtualModsToReal failed";
96 real_mask
= kBadMask
; // reset the return value, just in case.
102 XkbFreeKeyboard(xkb_desc
, 0, True
/* free all components */);
106 void ImeKeyboardX11::SetLockedModifiers() {
107 DCHECK(thread_checker_
.CalledOnValidThread());
109 // Always turn off num lock.
110 unsigned int affect_mask
= num_lock_mask_
;
111 unsigned int value_mask
= 0;
113 affect_mask
|= LockMask
;
114 value_mask
|= (caps_lock_is_enabled_
? LockMask
: 0);
116 XkbLockModifiers(gfx::GetXDisplay(), XkbUseCoreKbd
, affect_mask
, value_mask
);
119 bool ImeKeyboardX11::SetLayoutInternal(const std::string
& layout_name
,
121 if (!is_running_on_chrome_os_
) {
122 // We should not try to change a layout on Linux or inside ui_tests. Just
127 if (!CheckLayoutName(layout_name
))
130 if (!force
&& (last_layout_
== layout_name
)) {
131 DVLOG(1) << "The requested layout is already set: " << layout_name
;
135 DVLOG(1) << (force
? "Reapply" : "Set") << " layout: " << layout_name
;
137 const bool start_execution
= execute_queue_
.empty();
138 // If no setxkbmap command is in flight (i.e. start_execution is true),
139 // start the first one by explicitly calling MaybeExecuteSetLayoutCommand().
140 // If one or more setxkbmap commands are already in flight, just push the
141 // layout name to the queue. setxkbmap command for the layout will be called
142 // via OnSetLayoutFinish() callback later.
143 execute_queue_
.push(layout_name
);
145 MaybeExecuteSetLayoutCommand();
150 // Executes 'setxkbmap -layout ...' command asynchronously using a layout name
151 // in the |execute_queue_|. Do nothing if the queue is empty.
152 // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
153 void ImeKeyboardX11::MaybeExecuteSetLayoutCommand() {
154 if (execute_queue_
.empty())
156 const std::string layout_to_set
= execute_queue_
.front();
158 std::vector
<std::string
> argv
;
160 argv
.push_back(kSetxkbmapCommand
);
161 argv
.push_back("-layout");
162 argv
.push_back(layout_to_set
);
163 argv
.push_back("-synch");
165 base::Process process
= base::LaunchProcess(argv
, base::LaunchOptions());
166 if (!process
.IsValid()) {
167 DVLOG(1) << "Failed to execute setxkbmap: " << layout_to_set
;
168 execute_queue_
= std::queue
<std::string
>(); // clear the queue.
172 PollUntilChildFinish(process
.Handle());
174 DVLOG(1) << "ExecuteSetLayoutCommand: " << layout_to_set
175 << ": pid=" << process
.Pid();
178 // Delay and loop until child process finishes and call the callback.
179 void ImeKeyboardX11::PollUntilChildFinish(const base::ProcessHandle handle
) {
181 DVLOG(1) << "PollUntilChildFinish: poll for pid=" << base::GetProcId(handle
);
182 switch (base::GetTerminationStatus(handle
, &exit_code
)) {
183 case base::TERMINATION_STATUS_STILL_RUNNING
:
184 DVLOG(1) << "PollUntilChildFinish: Try waiting again";
185 base::MessageLoop::current()->PostDelayedTask(
187 base::Bind(&ImeKeyboardX11::PollUntilChildFinish
,
188 weak_factory_
.GetWeakPtr(),
190 base::TimeDelta::FromMilliseconds(kSetLayoutCommandCheckDelayMs
));
193 case base::TERMINATION_STATUS_NORMAL_TERMINATION
:
194 DVLOG(1) << "PollUntilChildFinish: Child process finished";
198 case base::TERMINATION_STATUS_ABNORMAL_TERMINATION
:
199 DVLOG(1) << "PollUntilChildFinish: Abnormal exit code: " << exit_code
;
210 bool ImeKeyboardX11::CapsLockIsEnabled() {
211 DCHECK(thread_checker_
.CalledOnValidThread());
213 XkbGetState(gfx::GetXDisplay(), XkbUseCoreKbd
, &status
);
214 return (status
.locked_mods
& LockMask
);
217 bool ImeKeyboardX11::SetAutoRepeatEnabled(bool enabled
) {
219 XAutoRepeatOn(gfx::GetXDisplay());
221 XAutoRepeatOff(gfx::GetXDisplay());
222 DVLOG(1) << "Set auto-repeat mode to: " << (enabled
? "on" : "off");
226 bool ImeKeyboardX11::GetAutoRepeatEnabled() {
227 XKeyboardState state
= {};
228 XGetKeyboardControl(gfx::GetXDisplay(), &state
);
229 return state
.global_auto_repeat
!= AutoRepeatModeOff
;
232 bool ImeKeyboardX11::SetAutoRepeatRate(const AutoRepeatRate
& rate
) {
233 DVLOG(1) << "Set auto-repeat rate to: "
234 << rate
.initial_delay_in_ms
<< " ms delay, "
235 << rate
.repeat_interval_in_ms
<< " ms interval";
236 if (XkbSetAutoRepeatRate(gfx::GetXDisplay(), XkbUseCoreKbd
,
237 rate
.initial_delay_in_ms
,
238 rate
.repeat_interval_in_ms
) != True
) {
239 DVLOG(1) << "Failed to set auto-repeat rate";
245 void ImeKeyboardX11::SetCapsLockEnabled(bool enable_caps_lock
) {
246 ImeKeyboard::SetCapsLockEnabled(enable_caps_lock
);
247 SetLockedModifiers();
250 bool ImeKeyboardX11::SetCurrentKeyboardLayoutByName(
251 const std::string
& layout_name
) {
252 if (SetLayoutInternal(layout_name
, false)) {
253 last_layout_
= layout_name
;
259 bool ImeKeyboardX11::ReapplyCurrentKeyboardLayout() {
260 if (last_layout_
.empty()) {
261 DVLOG(1) << "Can't reapply XKB layout: layout unknown";
264 return SetLayoutInternal(last_layout_
, true /* force */);
267 void ImeKeyboardX11::ReapplyCurrentModifierLockStatus() {
268 SetLockedModifiers();
271 void ImeKeyboardX11::DisableNumLock() {
272 SetCapsLockEnabled(caps_lock_is_enabled_
);
275 void ImeKeyboardX11::OnSetLayoutFinish() {
276 if (execute_queue_
.empty()) {
277 DVLOG(1) << "OnSetLayoutFinish: execute_queue_ is empty. "
278 << "base::LaunchProcess failed?";
281 execute_queue_
.pop();
282 MaybeExecuteSetLayoutCommand();
286 bool ImeKeyboard::GetAutoRepeatRateForTesting(AutoRepeatRate
* out_rate
) {
287 return XkbGetAutoRepeatRate(gfx::GetXDisplay(),
289 &(out_rate
->initial_delay_in_ms
),
290 &(out_rate
->repeat_interval_in_ms
)) == True
;
294 bool ImeKeyboard::CheckLayoutNameForTesting(const std::string
& layout_name
) {
295 return CheckLayoutName(layout_name
);
299 ImeKeyboard
* ImeKeyboard::Create() {
300 return new ImeKeyboardX11();
303 } // namespace input_method
304 } // namespace chromeos