summaryrefslogtreecommitdiffstats
path: root/chromeos/ime
diff options
context:
space:
mode:
authornona@chromium.org <nona@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-30 18:58:16 +0000
committernona@chromium.org <nona@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-03-30 18:58:16 +0000
commitda5cb8050d674ff048770411522628f66d99a297 (patch)
tree3165562799bfb893d4dab9c5480290d8d0c2e931 /chromeos/ime
parent43156477fa536cb65ff52bd5ba3ea52c73fd333f (diff)
downloadchromium_src-da5cb8050d674ff048770411522628f66d99a297.zip
chromium_src-da5cb8050d674ff048770411522628f66d99a297.tar.gz
chromium_src-da5cb8050d674ff048770411522628f66d99a297.tar.bz2
Move xkeyboard.cc from c/b/chromeos/input_method to chromeos/ime
BUG=164375 TEST=None TBR=sky Review URL: https://chromiumcodereview.appspot.com/12672008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@191535 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chromeos/ime')
-rw-r--r--chromeos/ime/mock_xkeyboard.cc67
-rw-r--r--chromeos/ime/mock_xkeyboard.h50
-rw-r--r--chromeos/ime/xkeyboard.cc378
-rw-r--r--chromeos/ime/xkeyboard.h133
-rw-r--r--chromeos/ime/xkeyboard_unittest.cc213
5 files changed, 841 insertions, 0 deletions
diff --git a/chromeos/ime/mock_xkeyboard.cc b/chromeos/ime/mock_xkeyboard.cc
new file mode 100644
index 0000000..a4a2f72
--- /dev/null
+++ b/chromeos/ime/mock_xkeyboard.cc
@@ -0,0 +1,67 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ime/mock_xkeyboard.h"
+
+namespace chromeos {
+namespace input_method {
+
+MockXKeyboard::MockXKeyboard()
+ : set_current_keyboard_layout_by_name_count_(0),
+ caps_lock_is_enabled_(false),
+ num_lock_is_enabled_(false) {
+}
+
+bool MockXKeyboard::SetCurrentKeyboardLayoutByName(
+ const std::string& layout_name) {
+ ++set_current_keyboard_layout_by_name_count_;
+ last_layout_ = layout_name;
+ return true;
+}
+
+bool MockXKeyboard::ReapplyCurrentKeyboardLayout() {
+ return true;
+}
+
+void MockXKeyboard::ReapplyCurrentModifierLockStatus() {
+}
+
+void MockXKeyboard::SetLockedModifiers(ModifierLockStatus new_caps_lock_status,
+ ModifierLockStatus new_num_lock_status) {
+ if (new_caps_lock_status != kDontChange) {
+ caps_lock_is_enabled_ =
+ (new_caps_lock_status == kEnableLock) ? true : false;
+ }
+ if (new_num_lock_status != kDontChange)
+ num_lock_is_enabled_ = (new_num_lock_status == kEnableLock) ? true : false;
+}
+
+void MockXKeyboard::SetNumLockEnabled(bool enable_num_lock) {
+ num_lock_is_enabled_ = enable_num_lock;
+}
+
+void MockXKeyboard::SetCapsLockEnabled(bool enable_caps_lock) {
+ caps_lock_is_enabled_ = enable_caps_lock;
+}
+
+bool MockXKeyboard::NumLockIsEnabled() {
+ return num_lock_is_enabled_;
+}
+
+bool MockXKeyboard::CapsLockIsEnabled() {
+ return caps_lock_is_enabled_;
+}
+
+unsigned int MockXKeyboard::GetNumLockMask() {
+ return 1;
+}
+
+void MockXKeyboard::GetLockedModifiers(bool* out_caps_lock_enabled,
+ bool* out_num_lock_enabled) {
+ *out_caps_lock_enabled = caps_lock_is_enabled_;
+ *out_num_lock_enabled = num_lock_is_enabled_;
+}
+
+} // namespace input_method
+} // namespace chromeos
diff --git a/chromeos/ime/mock_xkeyboard.h b/chromeos/ime/mock_xkeyboard.h
new file mode 100644
index 0000000..5c9ea2b
--- /dev/null
+++ b/chromeos/ime/mock_xkeyboard.h
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_IME_MOCK_XKEYBOARD_H_
+#define CHROMEOS_IME_MOCK_XKEYBOARD_H_
+
+#include "chromeos/ime/xkeyboard.h"
+
+#include <string>
+
+#include "base/compiler_specific.h"
+
+namespace chromeos {
+namespace input_method {
+
+class MockXKeyboard : public XKeyboard {
+ public:
+ MockXKeyboard();
+ virtual ~MockXKeyboard() {}
+
+ virtual bool SetCurrentKeyboardLayoutByName(
+ const std::string& layout_name) OVERRIDE;
+ virtual bool ReapplyCurrentKeyboardLayout() OVERRIDE;
+ virtual void ReapplyCurrentModifierLockStatus() OVERRIDE;
+ virtual void SetLockedModifiers(
+ ModifierLockStatus new_caps_lock_status,
+ ModifierLockStatus new_num_lock_status) OVERRIDE;
+ virtual void SetNumLockEnabled(bool enable_num_lock) OVERRIDE;
+ virtual void SetCapsLockEnabled(bool enable_caps_lock) OVERRIDE;
+ virtual bool NumLockIsEnabled() OVERRIDE;
+ virtual bool CapsLockIsEnabled() OVERRIDE;
+ virtual unsigned int GetNumLockMask() OVERRIDE;
+ virtual void GetLockedModifiers(bool* out_caps_lock_enabled,
+ bool* out_num_lock_enabled) OVERRIDE;
+
+ int set_current_keyboard_layout_by_name_count_;
+ std::string last_layout_;
+ bool caps_lock_is_enabled_;
+ bool num_lock_is_enabled_;
+ // TODO(yusukes): Add more variables for counting the numbers of the API calls
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockXKeyboard);
+};
+
+} // namespace input_method
+} // namespace chromeos
+
+#endif // CHROMEOS_IME_MOCK_XKEYBOARD_H_
diff --git a/chromeos/ime/xkeyboard.cc b/chromeos/ime/xkeyboard.cc
new file mode 100644
index 0000000..222b07d
--- /dev/null
+++ b/chromeos/ime/xkeyboard.cc
@@ -0,0 +1,378 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ime/xkeyboard.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <queue>
+#include <set>
+#include <utility>
+
+#include "base/chromeos/chromeos_version.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "base/process_util.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/threading/thread_checker.h"
+
+// These includes conflict with base/tracked_objects.h so must come last.
+#include <X11/XKBlib.h>
+#include <X11/Xlib.h>
+#include <glib.h>
+
+namespace chromeos {
+namespace input_method {
+namespace {
+
+Display* GetXDisplay() {
+ return base::MessagePumpForUI::GetDefaultXDisplay();
+}
+
+// The default keyboard layout name in the xorg config file.
+const char kDefaultLayoutName[] = "us";
+
+// The command we use to set the current XKB layout and modifier key mapping.
+// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
+const char kSetxkbmapCommand[] = "/usr/bin/setxkbmap";
+
+// A string for obtaining a mask value for Num Lock.
+const char kNumLockVirtualModifierString[] = "NumLock";
+
+// Returns false if |layout_name| contains a bad character.
+bool CheckLayoutName(const std::string& layout_name) {
+ static const char kValidLayoutNameCharacters[] =
+ "abcdefghijklmnopqrstuvwxyz0123456789()-_";
+
+ if (layout_name.empty()) {
+ DVLOG(1) << "Invalid layout_name: " << layout_name;
+ return false;
+ }
+
+ if (layout_name.find_first_not_of(kValidLayoutNameCharacters) !=
+ std::string::npos) {
+ DVLOG(1) << "Invalid layout_name: " << layout_name;
+ return false;
+ }
+
+ return true;
+}
+
+class XKeyboardImpl : public XKeyboard {
+ public:
+ XKeyboardImpl();
+ virtual ~XKeyboardImpl() {}
+
+ // Overridden from XKeyboard:
+ virtual bool SetCurrentKeyboardLayoutByName(
+ const std::string& layout_name) OVERRIDE;
+ virtual bool ReapplyCurrentKeyboardLayout() OVERRIDE;
+ virtual void ReapplyCurrentModifierLockStatus() OVERRIDE;
+ virtual void SetLockedModifiers(
+ ModifierLockStatus new_caps_lock_status,
+ ModifierLockStatus new_num_lock_status) OVERRIDE;
+ virtual void SetNumLockEnabled(bool enable_num_lock) OVERRIDE;
+ virtual void SetCapsLockEnabled(bool enable_caps_lock) OVERRIDE;
+ virtual bool NumLockIsEnabled() OVERRIDE;
+ virtual bool CapsLockIsEnabled() OVERRIDE;
+ virtual unsigned int GetNumLockMask() OVERRIDE;
+ virtual void GetLockedModifiers(bool* out_caps_lock_enabled,
+ bool* out_num_lock_enabled) OVERRIDE;
+
+ private:
+ // This function is used by SetLayout() and RemapModifierKeys(). Calls
+ // setxkbmap command if needed, and updates the last_full_layout_name_ cache.
+ bool SetLayoutInternal(const std::string& layout_name, bool force);
+
+ // Executes 'setxkbmap -layout ...' command asynchronously using a layout name
+ // in the |execute_queue_|. Do nothing if the queue is empty.
+ // TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
+ void MaybeExecuteSetLayoutCommand();
+
+ // Called when execve'd setxkbmap process exits.
+ static void OnSetLayoutFinish(pid_t pid, int status, XKeyboardImpl* self);
+
+ const bool is_running_on_chrome_os_;
+ unsigned int num_lock_mask_;
+
+ // The current Num Lock and Caps Lock status. If true, enabled.
+ bool current_num_lock_status_;
+ bool current_caps_lock_status_;
+ // The XKB layout name which we set last time like "us" and "us(dvorak)".
+ std::string current_layout_name_;
+
+ // A queue for executing setxkbmap one by one.
+ std::queue<std::string> execute_queue_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(XKeyboardImpl);
+};
+
+XKeyboardImpl::XKeyboardImpl()
+ : is_running_on_chrome_os_(base::chromeos::IsRunningOnChromeOS()) {
+ num_lock_mask_ = GetNumLockMask();
+
+ // web_input_event_aurax11.cc seems to assume that Mod2Mask is always assigned
+ // to Num Lock.
+ // TODO(yusukes): Check the assumption is really okay. If not, modify the Aura
+ // code, and then remove the CHECK below.
+ CHECK(!is_running_on_chrome_os_ || (num_lock_mask_ == Mod2Mask));
+ GetLockedModifiers(&current_caps_lock_status_, &current_num_lock_status_);
+}
+
+bool XKeyboardImpl::SetLayoutInternal(const std::string& layout_name,
+ bool force) {
+ if (!is_running_on_chrome_os_) {
+ // We should not try to change a layout on Linux or inside ui_tests. Just
+ // return true.
+ return true;
+ }
+
+ if (!CheckLayoutName(layout_name))
+ return false;
+
+ if (!force && (current_layout_name_ == layout_name)) {
+ DVLOG(1) << "The requested layout is already set: " << layout_name;
+ return true;
+ }
+
+ DVLOG(1) << (force ? "Reapply" : "Set") << " layout: " << layout_name;
+
+ const bool start_execution = execute_queue_.empty();
+ // If no setxkbmap command is in flight (i.e. start_execution is true),
+ // start the first one by explicitly calling MaybeExecuteSetLayoutCommand().
+ // If one or more setxkbmap commands are already in flight, just push the
+ // layout name to the queue. setxkbmap command for the layout will be called
+ // via OnSetLayoutFinish() callback later.
+ execute_queue_.push(layout_name);
+ if (start_execution)
+ MaybeExecuteSetLayoutCommand();
+
+ return true;
+}
+
+// Executes 'setxkbmap -layout ...' command asynchronously using a layout name
+// in the |execute_queue_|. Do nothing if the queue is empty.
+// TODO(yusukes): Use libxkbfile.so instead of the command (crosbug.com/13105)
+void XKeyboardImpl::MaybeExecuteSetLayoutCommand() {
+ if (execute_queue_.empty())
+ return;
+ const std::string layout_to_set = execute_queue_.front();
+
+ std::vector<std::string> argv;
+ base::ProcessHandle handle = base::kNullProcessHandle;
+
+ argv.push_back(kSetxkbmapCommand);
+ argv.push_back("-layout");
+ argv.push_back(layout_to_set);
+ argv.push_back("-synch");
+
+ if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) {
+ DVLOG(1) << "Failed to execute setxkbmap: " << layout_to_set;
+ execute_queue_ = std::queue<std::string>(); // clear the queue.
+ return;
+ }
+
+ // g_child_watch_add is necessary to prevent the process from becoming a
+ // zombie.
+ const base::ProcessId pid = base::GetProcId(handle);
+ g_child_watch_add(pid,
+ reinterpret_cast<GChildWatchFunc>(OnSetLayoutFinish),
+ this);
+ DVLOG(1) << "ExecuteSetLayoutCommand: " << layout_to_set << ": pid=" << pid;
+}
+
+bool XKeyboardImpl::NumLockIsEnabled() {
+ bool num_lock_enabled = false;
+ GetLockedModifiers(NULL /* Caps Lock */, &num_lock_enabled);
+ return num_lock_enabled;
+}
+
+bool XKeyboardImpl::CapsLockIsEnabled() {
+ bool caps_lock_enabled = false;
+ GetLockedModifiers(&caps_lock_enabled, NULL /* Num Lock */);
+ return caps_lock_enabled;
+}
+
+unsigned int XKeyboardImpl::GetNumLockMask() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ static const unsigned int kBadMask = 0;
+
+ unsigned int real_mask = kBadMask;
+ XkbDescPtr xkb_desc =
+ XkbGetKeyboard(GetXDisplay(), XkbAllComponentsMask, XkbUseCoreKbd);
+ if (!xkb_desc)
+ return kBadMask;
+
+ if (xkb_desc->dpy && xkb_desc->names && xkb_desc->names->vmods) {
+ const std::string string_to_find(kNumLockVirtualModifierString);
+ for (size_t i = 0; i < XkbNumVirtualMods; ++i) {
+ const unsigned int virtual_mod_mask = 1U << i;
+ char* virtual_mod_str_raw_ptr =
+ XGetAtomName(xkb_desc->dpy, xkb_desc->names->vmods[i]);
+ if (!virtual_mod_str_raw_ptr)
+ continue;
+ const std::string virtual_mod_str = virtual_mod_str_raw_ptr;
+ XFree(virtual_mod_str_raw_ptr);
+
+ if (string_to_find == virtual_mod_str) {
+ if (!XkbVirtualModsToReal(xkb_desc, virtual_mod_mask, &real_mask)) {
+ DVLOG(1) << "XkbVirtualModsToReal failed";
+ real_mask = kBadMask; // reset the return value, just in case.
+ }
+ break;
+ }
+ }
+ }
+ XkbFreeKeyboard(xkb_desc, 0, True /* free all components */);
+ return real_mask;
+}
+
+void XKeyboardImpl::GetLockedModifiers(bool* out_caps_lock_enabled,
+ bool* out_num_lock_enabled) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (out_num_lock_enabled && !num_lock_mask_) {
+ DVLOG(1) << "Cannot get locked modifiers. Num Lock mask unknown.";
+ if (out_caps_lock_enabled)
+ *out_caps_lock_enabled = false;
+ if (out_num_lock_enabled)
+ *out_num_lock_enabled = false;
+ return;
+ }
+
+ XkbStateRec status;
+ XkbGetState(GetXDisplay(), XkbUseCoreKbd, &status);
+ if (out_caps_lock_enabled)
+ *out_caps_lock_enabled = status.locked_mods & LockMask;
+ if (out_num_lock_enabled)
+ *out_num_lock_enabled = status.locked_mods & num_lock_mask_;
+}
+
+void XKeyboardImpl::SetLockedModifiers(ModifierLockStatus new_caps_lock_status,
+ ModifierLockStatus new_num_lock_status) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!num_lock_mask_) {
+ DVLOG(1) << "Cannot set locked modifiers. Num Lock mask unknown.";
+ return;
+ }
+
+ unsigned int affect_mask = 0;
+ unsigned int value_mask = 0;
+ if (new_caps_lock_status != kDontChange) {
+ affect_mask |= LockMask;
+ value_mask |= ((new_caps_lock_status == kEnableLock) ? LockMask : 0);
+ current_caps_lock_status_ = (new_caps_lock_status == kEnableLock);
+ }
+ if (new_num_lock_status != kDontChange) {
+ affect_mask |= num_lock_mask_;
+ value_mask |= ((new_num_lock_status == kEnableLock) ? num_lock_mask_ : 0);
+ current_num_lock_status_ = (new_num_lock_status == kEnableLock);
+ }
+
+ if (affect_mask)
+ XkbLockModifiers(GetXDisplay(), XkbUseCoreKbd, affect_mask, value_mask);
+}
+
+void XKeyboardImpl::SetNumLockEnabled(bool enable_num_lock) {
+ SetLockedModifiers(
+ kDontChange, enable_num_lock ? kEnableLock : kDisableLock);
+}
+
+void XKeyboardImpl::SetCapsLockEnabled(bool enable_caps_lock) {
+ SetLockedModifiers(
+ enable_caps_lock ? kEnableLock : kDisableLock, kDontChange);
+}
+
+bool XKeyboardImpl::SetCurrentKeyboardLayoutByName(
+ const std::string& layout_name) {
+ if (SetLayoutInternal(layout_name, false)) {
+ current_layout_name_ = layout_name;
+ return true;
+ }
+ return false;
+}
+
+bool XKeyboardImpl::ReapplyCurrentKeyboardLayout() {
+ if (current_layout_name_.empty()) {
+ DVLOG(1) << "Can't reapply XKB layout: layout unknown";
+ return false;
+ }
+ return SetLayoutInternal(current_layout_name_, true /* force */);
+}
+
+void XKeyboardImpl::ReapplyCurrentModifierLockStatus() {
+ SetLockedModifiers(current_caps_lock_status_ ? kEnableLock : kDisableLock,
+ current_num_lock_status_ ? kEnableLock : kDisableLock);
+}
+
+// static
+void XKeyboardImpl::OnSetLayoutFinish(pid_t pid,
+ int status,
+ XKeyboardImpl* self) {
+ DVLOG(1) << "OnSetLayoutFinish: pid=" << pid;
+ if (self->execute_queue_.empty()) {
+ DVLOG(1) << "OnSetLayoutFinish: execute_queue_ is empty. "
+ << "base::LaunchProcess failed? pid=" << pid;
+ return;
+ }
+ self->execute_queue_.pop();
+ self->MaybeExecuteSetLayoutCommand();
+}
+
+} // namespace
+
+// static
+bool XKeyboard::SetAutoRepeatEnabled(bool enabled) {
+ if (enabled)
+ XAutoRepeatOn(GetXDisplay());
+ else
+ XAutoRepeatOff(GetXDisplay());
+ DVLOG(1) << "Set auto-repeat mode to: " << (enabled ? "on" : "off");
+ return true;
+}
+
+// static
+bool XKeyboard::SetAutoRepeatRate(const AutoRepeatRate& rate) {
+ DVLOG(1) << "Set auto-repeat rate to: "
+ << rate.initial_delay_in_ms << " ms delay, "
+ << rate.repeat_interval_in_ms << " ms interval";
+ if (XkbSetAutoRepeatRate(GetXDisplay(), XkbUseCoreKbd,
+ rate.initial_delay_in_ms,
+ rate.repeat_interval_in_ms) != True) {
+ DVLOG(1) << "Failed to set auto-repeat rate";
+ return false;
+ }
+ return true;
+}
+
+// static
+bool XKeyboard::GetAutoRepeatEnabledForTesting() {
+ XKeyboardState state = {};
+ XGetKeyboardControl(GetXDisplay(), &state);
+ return state.global_auto_repeat != AutoRepeatModeOff;
+}
+
+// static
+bool XKeyboard::GetAutoRepeatRateForTesting(AutoRepeatRate* out_rate) {
+ return XkbGetAutoRepeatRate(GetXDisplay(), XkbUseCoreKbd,
+ &(out_rate->initial_delay_in_ms),
+ &(out_rate->repeat_interval_in_ms)) == True;
+}
+
+// static
+bool XKeyboard::CheckLayoutNameForTesting(const std::string& layout_name) {
+ return CheckLayoutName(layout_name);
+}
+
+// static
+XKeyboard* XKeyboard::Create() {
+ return new XKeyboardImpl();
+}
+
+} // namespace input_method
+} // namespace chromeos
diff --git a/chromeos/ime/xkeyboard.h b/chromeos/ime/xkeyboard.h
new file mode 100644
index 0000000..fc177a2
--- /dev/null
+++ b/chromeos/ime/xkeyboard.h
@@ -0,0 +1,133 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROMEOS_IME_XKEYBOARD_H_
+#define CHROMEOS_IME_XKEYBOARD_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chromeos/chromeos_export.h"
+
+namespace chromeos {
+namespace input_method {
+
+struct AutoRepeatRate {
+ AutoRepeatRate() : initial_delay_in_ms(0), repeat_interval_in_ms(0) {}
+ unsigned int initial_delay_in_ms;
+ unsigned int repeat_interval_in_ms;
+};
+
+enum ModifierLockStatus {
+ kDisableLock = 0,
+ kEnableLock,
+ kDontChange,
+};
+
+enum ModifierKey {
+ kSearchKey = 0, // Customizable.
+ kControlKey, // Customizable.
+ kAltKey, // Customizable.
+ kVoidKey,
+ kCapsLockKey,
+ // IMPORTANT: You should update kCustomizableKeys[] in .cc file, if you
+ // add a customizable key.
+ kNumModifierKeys,
+};
+
+class InputMethodUtil;
+
+class CHROMEOS_EXPORT XKeyboard {
+ public:
+ virtual ~XKeyboard() {}
+
+ // Sets the current keyboard layout to |layout_name|. This function does not
+ // change the current mapping of the modifier keys. Returns true on success.
+ virtual bool SetCurrentKeyboardLayoutByName(
+ const std::string& layout_name) = 0;
+
+ // Sets the current keyboard layout again. We have to call the function every
+ // time when "XI_HierarchyChanged" XInput2 event is sent to Chrome. See
+ // xinput_hierarchy_changed_event_listener.h for details.
+ virtual bool ReapplyCurrentKeyboardLayout() = 0;
+
+ // Updates keyboard LEDs on all keyboards.
+ // XKB asymmetrically propagates keyboard modifier indicator state changes to
+ // slave keyboards. If the state change is initiated from a client to the
+ // "core/master keyboard", XKB changes global state and pushes an indication
+ // change down to all keyboards. If the state change is initiated by one slave
+ // (physical) keyboard, it changes global state but only pushes an indicator
+ // state change down to that one keyboard.
+ // This function changes LEDs on all keyboards by explicitly updating the
+ // core/master keyboard.
+ virtual void ReapplyCurrentModifierLockStatus() = 0;
+
+ // Sets the Caps Lock and Num Lock status. Do not call the function from
+ // non-UI threads.
+ virtual void SetLockedModifiers(ModifierLockStatus new_caps_lock_status,
+ ModifierLockStatus new_num_lock_status) = 0;
+
+ // Sets the num lock status to |enable_num_lock|. Do not call the function
+ // from non-UI threads.
+ virtual void SetNumLockEnabled(bool enable_num_lock) = 0;
+
+ // Sets the caps lock status to |enable_caps_lock|. Do not call the function
+ // from non-UI threads.
+ virtual void SetCapsLockEnabled(bool enable_caps_lock) = 0;
+
+ // Returns true if num lock is enabled. Do not call the function from non-UI
+ // threads.
+ virtual bool NumLockIsEnabled() = 0;
+
+ // Returns true if caps lock is enabled. Do not call the function from non-UI
+ // threads.
+ virtual bool CapsLockIsEnabled() = 0;
+
+ // Returns a mask (e.g. 1U<<4) for Num Lock. On error, returns 0. Do not call
+ // the function from non-UI threads.
+ // TODO(yusukes): Move this and webdriver::GetXModifierMask() functions in
+ // chrome/test/webdriver/keycode_text_conversion_x.cc to ui/base/x/x11_util.
+ // The two functions are almost the same.
+ virtual unsigned int GetNumLockMask() = 0;
+
+ // Set true on |out_caps_lock_enabled| if Caps Lock is enabled. Set true on
+ // |out_num_lock_enabled| if Num Lock is enabled. Both out parameters can be
+ // NULL. Do not call the function from non-UI threads.
+ virtual void GetLockedModifiers(bool* out_caps_lock_enabled,
+ bool* out_num_lock_enabled) = 0;
+
+ // Turns on and off the auto-repeat of the keyboard. Returns true on success.
+ // Do not call the function from non-UI threads.
+ // TODO(yusukes): Make this function non-static so we can mock it.
+ static CHROMEOS_EXPORT bool SetAutoRepeatEnabled(bool enabled);
+
+ // Sets the auto-repeat rate of the keyboard, initial delay in ms, and repeat
+ // interval in ms. Returns true on success. Do not call the function from
+ // non-UI threads.
+ // TODO(yusukes): Make this function non-static so we can mock it.
+ static CHROMEOS_EXPORT bool SetAutoRepeatRate(const AutoRepeatRate& rate);
+
+ // Returns true if auto repeat is enabled. This function is protected: for
+ // testability.
+ static CHROMEOS_EXPORT bool GetAutoRepeatEnabledForTesting();
+
+ // On success, set current auto repeat rate on |out_rate| and returns true.
+ // Returns false otherwise. This function is protected: for testability.
+ static CHROMEOS_EXPORT bool GetAutoRepeatRateForTesting(
+ AutoRepeatRate* out_rate);
+
+ // Returns false if |layout_name| contains a bad character.
+ static CHROMEOS_EXPORT bool CheckLayoutNameForTesting(
+ const std::string& layout_name);
+
+ // Note: At this moment, classes other than InputMethodManager should not
+ // instantiate the XKeyboard class.
+ static XKeyboard* Create();
+};
+
+} // namespace input_method
+} // namespace chromeos
+
+#endif // CHROMEOS_IME_XKEYBOARD_H_
diff --git a/chromeos/ime/xkeyboard_unittest.cc b/chromeos/ime/xkeyboard_unittest.cc
new file mode 100644
index 0000000..6048c4c
--- /dev/null
+++ b/chromeos/ime/xkeyboard_unittest.cc
@@ -0,0 +1,213 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chromeos/ime/xkeyboard.h"
+
+#include <algorithm>
+#include <set>
+#include <string>
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/message_loop.h"
+#include "chromeos/ime/input_method_whitelist.h"
+#include "chromeos/ime/mock_input_method_delegate.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#include <X11/Xlib.h>
+
+namespace chromeos {
+namespace input_method {
+
+namespace {
+
+class XKeyboardTest : public testing::Test {
+ public:
+ XKeyboardTest() {
+ }
+
+ virtual void SetUp() {
+ xkey_.reset(XKeyboard::Create());
+ }
+
+ virtual void TearDown() {
+ xkey_.reset();
+ }
+
+ InputMethodWhitelist whitelist_;
+ scoped_ptr<XKeyboard> xkey_;
+
+ MessageLoopForUI message_loop_;
+};
+
+// Returns true if X display is available.
+bool DisplayAvailable() {
+ return (base::MessagePumpForUI::GetDefaultXDisplay() != NULL);
+}
+
+} // namespace
+
+// Tests CheckLayoutName() function.
+TEST_F(XKeyboardTest, TestCheckLayoutName) {
+ // CheckLayoutName should not accept non-alphanumeric characters
+ // except "()-_".
+ EXPECT_FALSE(XKeyboard::CheckLayoutNameForTesting("us!"));
+ EXPECT_FALSE(XKeyboard::CheckLayoutNameForTesting("us; /bin/sh"));
+ EXPECT_TRUE(XKeyboard::CheckLayoutNameForTesting("ab-c_12"));
+
+ // CheckLayoutName should not accept upper-case ascii characters.
+ EXPECT_FALSE(XKeyboard::CheckLayoutNameForTesting("US"));
+
+ // CheckLayoutName should accept lower-case ascii characters.
+ for (int c = 'a'; c <= 'z'; ++c) {
+ EXPECT_TRUE(XKeyboard::CheckLayoutNameForTesting(std::string(3, c)));
+ }
+
+ // CheckLayoutName should accept numbers.
+ for (int c = '0'; c <= '9'; ++c) {
+ EXPECT_TRUE(XKeyboard::CheckLayoutNameForTesting(std::string(3, c)));
+ }
+
+ // CheckLayoutName should accept a layout with a variant name.
+ EXPECT_TRUE(XKeyboard::CheckLayoutNameForTesting("us(dvorak)"));
+ EXPECT_TRUE(XKeyboard::CheckLayoutNameForTesting("jp"));
+}
+
+TEST_F(XKeyboardTest, TestSetCapsLockEnabled) {
+ if (!DisplayAvailable()) {
+ // Do not fail the test to allow developers to run unit_tests without an X
+ // server (e.g. via ssh). Note that both try bots and waterfall always have
+ // an X server for running browser_tests.
+ DVLOG(1) << "X server is not available. Skip the test.";
+ return;
+ }
+ const bool initial_lock_state = xkey_->CapsLockIsEnabled();
+ xkey_->SetCapsLockEnabled(true);
+ EXPECT_TRUE(xkey_->CapsLockIsEnabled());
+ xkey_->SetCapsLockEnabled(false);
+ EXPECT_FALSE(xkey_->CapsLockIsEnabled());
+ xkey_->SetCapsLockEnabled(true);
+ EXPECT_TRUE(xkey_->CapsLockIsEnabled());
+ xkey_->SetCapsLockEnabled(false);
+ EXPECT_FALSE(xkey_->CapsLockIsEnabled());
+ xkey_->SetCapsLockEnabled(initial_lock_state);
+}
+
+TEST_F(XKeyboardTest, TestSetNumLockEnabled) {
+ if (!DisplayAvailable()) {
+ DVLOG(1) << "X server is not available. Skip the test.";
+ return;
+ }
+ const unsigned int num_lock_mask = xkey_->GetNumLockMask();
+ ASSERT_NE(0U, num_lock_mask);
+
+ const bool initial_lock_state = xkey_->NumLockIsEnabled();
+ xkey_->SetNumLockEnabled(true);
+ EXPECT_TRUE(xkey_->NumLockIsEnabled());
+ xkey_->SetNumLockEnabled(false);
+ EXPECT_FALSE(xkey_->NumLockIsEnabled());
+ xkey_->SetNumLockEnabled(true);
+ EXPECT_TRUE(xkey_->NumLockIsEnabled());
+ xkey_->SetNumLockEnabled(false);
+ EXPECT_FALSE(xkey_->NumLockIsEnabled());
+ xkey_->SetNumLockEnabled(initial_lock_state);
+}
+
+TEST_F(XKeyboardTest, TestSetCapsLockAndNumLockAtTheSameTime) {
+ if (!DisplayAvailable()) {
+ DVLOG(1) << "X server is not available. Skip the test.";
+ return;
+ }
+ const unsigned int num_lock_mask = xkey_->GetNumLockMask();
+ ASSERT_NE(0U, num_lock_mask);
+
+ const bool initial_caps_lock_state = xkey_->CapsLockIsEnabled();
+ const bool initial_num_lock_state = xkey_->NumLockIsEnabled();
+
+ // Flip both.
+ xkey_->SetLockedModifiers(
+ initial_caps_lock_state ? kDisableLock : kEnableLock,
+ initial_num_lock_state ? kDisableLock : kEnableLock);
+ EXPECT_EQ(!initial_caps_lock_state, xkey_->CapsLockIsEnabled());
+ EXPECT_EQ(!initial_num_lock_state, xkey_->NumLockIsEnabled());
+
+ // Flip Caps Lock.
+ xkey_->SetLockedModifiers(
+ initial_caps_lock_state ? kEnableLock : kDisableLock,
+ kDontChange);
+ // Use GetLockedModifiers() for verifying the result.
+ bool c, n;
+ xkey_->GetLockedModifiers(&c, &n);
+ EXPECT_EQ(initial_caps_lock_state, c);
+ EXPECT_EQ(!initial_num_lock_state, n);
+
+ // Flip both.
+ xkey_->SetLockedModifiers(
+ initial_caps_lock_state ? kDisableLock : kEnableLock,
+ initial_num_lock_state ? kEnableLock : kDisableLock);
+ EXPECT_EQ(!initial_caps_lock_state, xkey_->CapsLockIsEnabled());
+ EXPECT_EQ(initial_num_lock_state, xkey_->NumLockIsEnabled());
+
+ // Flip Num Lock.
+ xkey_->SetLockedModifiers(
+ kDontChange,
+ initial_num_lock_state ? kDisableLock : kEnableLock);
+ xkey_->GetLockedModifiers(&c, &n);
+ EXPECT_EQ(!initial_caps_lock_state, c);
+ EXPECT_EQ(!initial_num_lock_state, n);
+
+ // Flip both to restore the initial state.
+ xkey_->SetLockedModifiers(
+ initial_caps_lock_state ? kEnableLock : kDisableLock,
+ initial_num_lock_state ? kEnableLock : kDisableLock);
+ EXPECT_EQ(initial_caps_lock_state, xkey_->CapsLockIsEnabled());
+ EXPECT_EQ(initial_num_lock_state, xkey_->NumLockIsEnabled());
+
+ // No-op SetLockedModifiers call.
+ xkey_->SetLockedModifiers(kDontChange, kDontChange);
+ EXPECT_EQ(initial_caps_lock_state, xkey_->CapsLockIsEnabled());
+ EXPECT_EQ(initial_num_lock_state, xkey_->NumLockIsEnabled());
+
+ // No-op GetLockedModifiers call. Confirm it does not crash.
+ xkey_->GetLockedModifiers(NULL, NULL);
+}
+
+TEST_F(XKeyboardTest, TestSetAutoRepeatEnabled) {
+ if (!DisplayAvailable()) {
+ DVLOG(1) << "X server is not available. Skip the test.";
+ return;
+ }
+ const bool state = XKeyboard::GetAutoRepeatEnabledForTesting();
+ XKeyboard::SetAutoRepeatEnabled(!state);
+ EXPECT_EQ(!state, XKeyboard::GetAutoRepeatEnabledForTesting());
+ // Restore the initial state.
+ XKeyboard::SetAutoRepeatEnabled(state);
+ EXPECT_EQ(state, XKeyboard::GetAutoRepeatEnabledForTesting());
+}
+
+TEST_F(XKeyboardTest, TestSetAutoRepeatRate) {
+ if (!DisplayAvailable()) {
+ DVLOG(1) << "X server is not available. Skip the test.";
+ return;
+ }
+ AutoRepeatRate rate;
+ EXPECT_TRUE(XKeyboard::GetAutoRepeatRateForTesting(&rate));
+
+ AutoRepeatRate tmp(rate);
+ ++tmp.initial_delay_in_ms;
+ ++tmp.repeat_interval_in_ms;
+ EXPECT_TRUE(XKeyboard::SetAutoRepeatRate(tmp));
+ EXPECT_TRUE(XKeyboard::GetAutoRepeatRateForTesting(&tmp));
+ EXPECT_EQ(rate.initial_delay_in_ms + 1, tmp.initial_delay_in_ms);
+ EXPECT_EQ(rate.repeat_interval_in_ms + 1, tmp.repeat_interval_in_ms);
+
+ // Restore the initial state.
+ EXPECT_TRUE(XKeyboard::SetAutoRepeatRate(rate));
+ EXPECT_TRUE(XKeyboard::GetAutoRepeatRateForTesting(&tmp));
+ EXPECT_EQ(rate.initial_delay_in_ms, tmp.initial_delay_in_ms);
+ EXPECT_EQ(rate.repeat_interval_in_ms, tmp.repeat_interval_in_ms);
+}
+
+} // namespace input_method
+} // namespace chromeos