diff options
author | nona@chromium.org <nona@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-30 18:58:16 +0000 |
---|---|---|
committer | nona@chromium.org <nona@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-30 18:58:16 +0000 |
commit | da5cb8050d674ff048770411522628f66d99a297 (patch) | |
tree | 3165562799bfb893d4dab9c5480290d8d0c2e931 /chromeos/ime | |
parent | 43156477fa536cb65ff52bd5ba3ea52c73fd333f (diff) | |
download | chromium_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.cc | 67 | ||||
-rw-r--r-- | chromeos/ime/mock_xkeyboard.h | 50 | ||||
-rw-r--r-- | chromeos/ime/xkeyboard.cc | 378 | ||||
-rw-r--r-- | chromeos/ime/xkeyboard.h | 133 | ||||
-rw-r--r-- | chromeos/ime/xkeyboard_unittest.cc | 213 |
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(¤t_caps_lock_status_, ¤t_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 |