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 <algorithm>
8#include <set>
9#include <string>
10
11#include <gtest/gtest.h>
12#include <X11/Xlib.h>
13
14#include "base/logging.h"
15
16namespace chromeos {
17namespace input_method {
18
19namespace {
20
21// Returns a ModifierMap object that contains the following mapping:
22// - kSearchKey is mapped to |search|.
23// - kLeftControl key is mapped to |control|.
24// - kLeftAlt key is mapped to |alt|.
25ModifierMap GetMap(ModifierKey search, ModifierKey control, ModifierKey alt) {
26  ModifierMap modifier_key;
27  // Use the Search key as |search|.
28  modifier_key.push_back(ModifierKeyPair(kSearchKey, search));
29  modifier_key.push_back(ModifierKeyPair(kLeftControlKey, control));
30  modifier_key.push_back(ModifierKeyPair(kLeftAltKey, alt));
31  return modifier_key;
32}
33
34// Checks |modifier_map| and returns true if the following conditions are met:
35// - kSearchKey is mapped to |search|.
36// - kLeftControl key is mapped to |control|.
37// - kLeftAlt key is mapped to |alt|.
38bool CheckMap(const ModifierMap& modifier_map,
39              ModifierKey search, ModifierKey control, ModifierKey alt) {
40  ModifierMap::const_iterator begin = modifier_map.begin();
41  ModifierMap::const_iterator end = modifier_map.end();
42  if ((std::count(begin, end, ModifierKeyPair(kSearchKey, search)) == 1) &&
43      (std::count(begin, end,
44                  ModifierKeyPair(kLeftControlKey, control)) == 1) &&
45      (std::count(begin, end, ModifierKeyPair(kLeftAltKey, alt)) == 1)) {
46    return true;
47  }
48  return false;
49}
50
51// Returns true if X display is available.
52bool DisplayAvailable() {
53  Display* display = XOpenDisplay(NULL);
54  if (!display) {
55    return false;
56  }
57  XCloseDisplay(display);
58  return true;
59}
60
61}  // namespace
62
63// Tests CreateFullXkbLayoutName() function.
64TEST(XKeyboardTest, TestCreateFullXkbLayoutNameBasic) {
65  // CreateFullXkbLayoutName should not accept an empty |layout_name|.
66  EXPECT_STREQ("", CreateFullXkbLayoutName(
67      "", GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
68
69  // CreateFullXkbLayoutName should not accept an empty ModifierMap.
70  EXPECT_STREQ("", CreateFullXkbLayoutName("us", ModifierMap()).c_str());
71
72  // CreateFullXkbLayoutName should not accept an incomplete ModifierMap.
73  ModifierMap tmp_map = GetMap(kVoidKey, kVoidKey, kVoidKey);
74  tmp_map.pop_back();
75  EXPECT_STREQ("", CreateFullXkbLayoutName("us", tmp_map).c_str());
76
77  // CreateFullXkbLayoutName should not accept redundant ModifierMaps.
78  tmp_map = GetMap(kVoidKey, kVoidKey, kVoidKey);
79  tmp_map.push_back(ModifierKeyPair(kSearchKey, kVoidKey));  // two search maps
80  EXPECT_STREQ("", CreateFullXkbLayoutName("us", tmp_map).c_str());
81  tmp_map = GetMap(kVoidKey, kVoidKey, kVoidKey);
82  tmp_map.push_back(ModifierKeyPair(kLeftControlKey, kVoidKey));  // two ctrls
83  EXPECT_STREQ("", CreateFullXkbLayoutName("us", tmp_map).c_str());
84  tmp_map = GetMap(kVoidKey, kVoidKey, kVoidKey);
85  tmp_map.push_back(ModifierKeyPair(kLeftAltKey, kVoidKey));  // two alts.
86  EXPECT_STREQ("", CreateFullXkbLayoutName("us", tmp_map).c_str());
87
88  // CreateFullXkbLayoutName should not accept invalid ModifierMaps.
89  tmp_map = GetMap(kVoidKey, kVoidKey, kVoidKey);
90  tmp_map.push_back(ModifierKeyPair(kVoidKey, kSearchKey));  // can't remap void
91  EXPECT_STREQ("", CreateFullXkbLayoutName("us", tmp_map).c_str());
92  tmp_map = GetMap(kVoidKey, kVoidKey, kVoidKey);
93  tmp_map.push_back(ModifierKeyPair(kCapsLockKey, kSearchKey));  // ditto
94  EXPECT_STREQ("", CreateFullXkbLayoutName("us", tmp_map).c_str());
95
96  // CreateFullXkbLayoutName can remap Search/Ctrl/Alt to CapsLock.
97  EXPECT_STREQ("us+chromeos(capslock_disabled_disabled)",
98               CreateFullXkbLayoutName(
99                   "us",
100                   GetMap(kCapsLockKey, kVoidKey, kVoidKey)).c_str());
101  EXPECT_STREQ("us+chromeos(disabled_capslock_disabled)",
102               CreateFullXkbLayoutName(
103                   "us",
104                   GetMap(kVoidKey, kCapsLockKey, kVoidKey)).c_str());
105  EXPECT_STREQ("us+chromeos(disabled_disabled_capslock)",
106               CreateFullXkbLayoutName(
107                   "us",
108                   GetMap(kVoidKey, kVoidKey, kCapsLockKey)).c_str());
109
110  // CreateFullXkbLayoutName should not accept non-alphanumeric characters
111  // except "()-_".
112  EXPECT_STREQ("", CreateFullXkbLayoutName(
113      "us!", GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
114  EXPECT_STREQ("", CreateFullXkbLayoutName(
115      "us; /bin/sh", GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
116  EXPECT_STREQ("ab-c_12+chromeos(disabled_disabled_disabled),us",
117               CreateFullXkbLayoutName(
118                   "ab-c_12",
119                   GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
120
121  // CreateFullXkbLayoutName should not accept upper-case ascii characters.
122  EXPECT_STREQ("", CreateFullXkbLayoutName(
123      "US", GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
124
125  // CreateFullXkbLayoutName should accept lower-case ascii characters.
126  for (int c = 'a'; c <= 'z'; ++c) {
127    EXPECT_STRNE("", CreateFullXkbLayoutName(
128        std::string(3, c),
129        GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
130  }
131
132  // CreateFullXkbLayoutName should accept numbers.
133  for (int c = '0'; c <= '9'; ++c) {
134    EXPECT_STRNE("", CreateFullXkbLayoutName(
135        std::string(3, c),
136        GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
137  }
138
139  // CreateFullXkbLayoutName should accept a layout with a variant name.
140  EXPECT_STREQ("us(dvorak)+chromeos(disabled_disabled_disabled)",
141               CreateFullXkbLayoutName(
142                   "us(dvorak)",
143                   GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
144  // TODO: Re-enable this when the stub is fixed to handle it.
145  // EXPECT_STREQ("gb(extd)+chromeos(disabled_disabled_disabled),us",
146  //              CreateFullXkbLayoutName(
147  //                  "gb(extd)",
148  //                  GetMap(kVoidKey, kVoidKey, kVoidKey)).c_str());
149  EXPECT_STREQ("gb(extd)+", CreateFullXkbLayoutName(
150      "gb(extd)",
151      GetMap(kVoidKey, kVoidKey, kVoidKey)).substr(0, 9).c_str());
152  EXPECT_STREQ("jp+", CreateFullXkbLayoutName(
153      "jp", GetMap(kVoidKey, kVoidKey, kVoidKey)).substr(0, 3).c_str());
154
155  // When the layout name is not "us", the second layout should be added.
156  EXPECT_EQ(std::string::npos, CreateFullXkbLayoutName(
157      "us", GetMap(kVoidKey, kVoidKey, kVoidKey)).find(",us"));
158  EXPECT_EQ(std::string::npos, CreateFullXkbLayoutName(
159      "us(dvorak)", GetMap(kVoidKey, kVoidKey, kVoidKey)).find(",us"));
160  EXPECT_NE(std::string::npos, CreateFullXkbLayoutName(
161      "gb(extd)", GetMap(kVoidKey, kVoidKey, kVoidKey)).find(",us"));
162  EXPECT_NE(std::string::npos, CreateFullXkbLayoutName(
163      "jp", GetMap(kVoidKey, kVoidKey, kVoidKey)).find(",us"));
164}
165
166// Tests if CreateFullXkbLayoutName and ExtractLayoutNameFromFullXkbLayoutName
167// functions could handle all combinations of modifier remapping.
168TEST(XKeyboardTest, TestCreateFullXkbLayoutNameModifierKeys) {
169  std::set<std::string> layouts;
170  for (int i = 0; i < static_cast<int>(kNumModifierKeys); ++i) {
171    for (int j = 0; j < static_cast<int>(kNumModifierKeys); ++j) {
172      for (int k = 0; k < static_cast<int>(kNumModifierKeys); ++k) {
173        const std::string layout = CreateFullXkbLayoutName(
174            "us", GetMap(ModifierKey(i), ModifierKey(j), ModifierKey(k)));
175        // CreateFullXkbLayoutName should succeed (i.e. should not return "".)
176        EXPECT_STREQ("us+", layout.substr(0, 3).c_str())
177            << "layout: " << layout;
178        // All 4*3*3 layouts should be different.
179        EXPECT_TRUE(layouts.insert(layout).second) << "layout: " << layout;
180      }
181    }
182  }
183}
184
185TEST(XKeyboardTest, TestSetCapsLockIsEnabled) {
186  if (!DisplayAvailable()) {
187    return;
188  }
189  const bool initial_lock_state = CapsLockIsEnabled();
190  SetCapsLockEnabled(true);
191  EXPECT_TRUE(CapsLockIsEnabled());
192  SetCapsLockEnabled(false);
193  EXPECT_FALSE(CapsLockIsEnabled());
194  SetCapsLockEnabled(true);
195  EXPECT_TRUE(CapsLockIsEnabled());
196  SetCapsLockEnabled(false);
197  EXPECT_FALSE(CapsLockIsEnabled());
198  SetCapsLockEnabled(initial_lock_state);
199}
200
201TEST(XKeyboardTest, TestContainsModifierKeyAsReplacement) {
202  EXPECT_FALSE(ContainsModifierKeyAsReplacement(
203      GetMap(kVoidKey, kVoidKey, kVoidKey), kCapsLockKey));
204  EXPECT_TRUE(ContainsModifierKeyAsReplacement(
205      GetMap(kCapsLockKey, kVoidKey, kVoidKey), kCapsLockKey));
206  EXPECT_TRUE(ContainsModifierKeyAsReplacement(
207      GetMap(kVoidKey, kCapsLockKey, kVoidKey), kCapsLockKey));
208  EXPECT_TRUE(ContainsModifierKeyAsReplacement(
209      GetMap(kVoidKey, kVoidKey, kCapsLockKey), kCapsLockKey));
210  EXPECT_TRUE(ContainsModifierKeyAsReplacement(
211      GetMap(kCapsLockKey, kCapsLockKey, kVoidKey), kCapsLockKey));
212  EXPECT_TRUE(ContainsModifierKeyAsReplacement(
213      GetMap(kCapsLockKey, kCapsLockKey, kCapsLockKey), kCapsLockKey));
214  EXPECT_TRUE(ContainsModifierKeyAsReplacement(
215      GetMap(kSearchKey, kVoidKey, kVoidKey), kSearchKey));
216}
217
218}  // namespace input_method
219}  // namespace chromeos
220