1// Copyright (c) 2013 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/test/chromedriver/keycode_text_conversion.h"
6
7#include <algorithm>
8#include <X11/keysym.h>
9#include <X11/XKBlib.h>
10#include <X11/Xlib.h>
11#include <X11/Xutil.h>
12
13#include "base/strings/utf_string_conversions.h"
14#include "chrome/test/chromedriver/chrome/ui_events.h"
15#include "ui/base/x/x11_util.h"
16#include "ui/events/keycodes/keyboard_code_conversion_x.h"
17
18namespace {
19
20struct KeyCodeAndXKeyCode {
21  ui::KeyboardCode key_code;
22  int x_key_code;
23};
24
25// Contains a list of keyboard codes, in order, with their corresponding
26// X key code. This list is not complete.
27// TODO(kkania): Merge this table with the existing one in
28// keyboard_code_conversion_x.cc.
29KeyCodeAndXKeyCode kKeyCodeToXKeyCode[] = {
30  { ui::VKEY_BACK, 22 },
31  { ui::VKEY_TAB, 23 },
32  { ui::VKEY_RETURN, 36 },
33  { ui::VKEY_SHIFT, 50 },
34  { ui::VKEY_CONTROL, 37 },
35  { ui::VKEY_MENU, 64 },
36  { ui::VKEY_CAPITAL, 66 },
37  { ui::VKEY_HANGUL, 130 },
38  { ui::VKEY_HANJA, 131 },
39  { ui::VKEY_ESCAPE, 9 },
40  { ui::VKEY_SPACE, 65 },
41  { ui::VKEY_PRIOR, 112 },
42  { ui::VKEY_NEXT, 117 },
43  { ui::VKEY_END, 115 },
44  { ui::VKEY_HOME, 110 },
45  { ui::VKEY_LEFT, 113 },
46  { ui::VKEY_UP, 111 },
47  { ui::VKEY_RIGHT, 114 },
48  { ui::VKEY_DOWN, 116 },
49  { ui::VKEY_INSERT, 118 },
50  { ui::VKEY_DELETE, 119 },
51  { ui::VKEY_0, 19 },
52  { ui::VKEY_1, 10 },
53  { ui::VKEY_2, 11 },
54  { ui::VKEY_3, 12 },
55  { ui::VKEY_4, 13 },
56  { ui::VKEY_5, 14 },
57  { ui::VKEY_6, 15 },
58  { ui::VKEY_7, 16 },
59  { ui::VKEY_8, 17 },
60  { ui::VKEY_9, 18 },
61  { ui::VKEY_A, 38 },
62  { ui::VKEY_B, 56 },
63  { ui::VKEY_C, 54 },
64  { ui::VKEY_D, 40 },
65  { ui::VKEY_E, 26 },
66  { ui::VKEY_F, 41 },
67  { ui::VKEY_G, 42 },
68  { ui::VKEY_H, 43 },
69  { ui::VKEY_I, 31 },
70  { ui::VKEY_J, 44 },
71  { ui::VKEY_K, 45 },
72  { ui::VKEY_L, 46 },
73  { ui::VKEY_M, 58 },
74  { ui::VKEY_N, 57 },
75  { ui::VKEY_O, 32 },
76  { ui::VKEY_P, 33 },
77  { ui::VKEY_Q, 24 },
78  { ui::VKEY_R, 27 },
79  { ui::VKEY_S, 39 },
80  { ui::VKEY_T, 28 },
81  { ui::VKEY_U, 30 },
82  { ui::VKEY_V, 55 },
83  { ui::VKEY_W, 25 },
84  { ui::VKEY_X, 53 },
85  { ui::VKEY_Y, 29 },
86  { ui::VKEY_Z, 52 },
87  { ui::VKEY_LWIN, 133 },
88  { ui::VKEY_NUMPAD0, 90 },
89  { ui::VKEY_NUMPAD1, 87 },
90  { ui::VKEY_NUMPAD2, 88 },
91  { ui::VKEY_NUMPAD3, 89 },
92  { ui::VKEY_NUMPAD4, 83 },
93  { ui::VKEY_NUMPAD5, 84 },
94  { ui::VKEY_NUMPAD6, 85 },
95  { ui::VKEY_NUMPAD7, 79 },
96  { ui::VKEY_NUMPAD8, 80 },
97  { ui::VKEY_NUMPAD9, 81 },
98  { ui::VKEY_MULTIPLY, 63 },
99  { ui::VKEY_ADD, 86 },
100  { ui::VKEY_SUBTRACT, 82 },
101  { ui::VKEY_DECIMAL, 129 },
102  { ui::VKEY_DIVIDE, 106 },
103  { ui::VKEY_F1, 67 },
104  { ui::VKEY_F2, 68 },
105  { ui::VKEY_F3, 69 },
106  { ui::VKEY_F4, 70 },
107  { ui::VKEY_F5, 71 },
108  { ui::VKEY_F6, 72 },
109  { ui::VKEY_F7, 73 },
110  { ui::VKEY_F8, 74 },
111  { ui::VKEY_F9, 75 },
112  { ui::VKEY_F10, 76 },
113  { ui::VKEY_F11, 95 },
114  { ui::VKEY_F12, 96 },
115  { ui::VKEY_NUMLOCK, 77 },
116  { ui::VKEY_SCROLL, 78 },
117  { ui::VKEY_OEM_1, 47 },
118  { ui::VKEY_OEM_PLUS, 21 },
119  { ui::VKEY_OEM_COMMA, 59 },
120  { ui::VKEY_OEM_MINUS, 20 },
121  { ui::VKEY_OEM_PERIOD, 60 },
122  { ui::VKEY_OEM_2, 61 },
123  { ui::VKEY_OEM_3, 49 },
124  { ui::VKEY_OEM_4, 34 },
125  { ui::VKEY_OEM_5, 51 },
126  { ui::VKEY_OEM_6, 35 },
127  { ui::VKEY_OEM_7, 48 }
128};
129
130// Uses to compare two KeyCodeAndXKeyCode structs based on their key code.
131bool operator<(const KeyCodeAndXKeyCode& a, const KeyCodeAndXKeyCode& b) {
132  return a.key_code < b.key_code;
133}
134
135// Returns the equivalent X key code for the given key code. Returns -1 if
136// no X equivalent was found.
137int KeyboardCodeToXKeyCode(ui::KeyboardCode key_code) {
138  KeyCodeAndXKeyCode find;
139  find.key_code = key_code;
140  const KeyCodeAndXKeyCode* found = std::lower_bound(
141      kKeyCodeToXKeyCode, kKeyCodeToXKeyCode + arraysize(kKeyCodeToXKeyCode),
142      find);
143  if (found >= kKeyCodeToXKeyCode + arraysize(kKeyCodeToXKeyCode) ||
144      found->key_code != key_code)
145    return -1;
146  return found->x_key_code;
147}
148
149// Gets the X modifier mask (Mod1Mask through Mod5Mask) for the given
150// modifier. Only checks the alt, meta, and num lock keys currently.
151// Returns true on success.
152bool GetXModifierMask(Display* display, int modifier, int* x_modifier) {
153  XModifierKeymap* mod_map = XGetModifierMapping(display);
154  bool found = false;
155  int max_mod_keys = mod_map->max_keypermod;
156  for (int mod_index = 0; mod_index <= 8; ++mod_index) {
157    for (int key_index = 0; key_index < max_mod_keys; ++key_index) {
158      int key = mod_map->modifiermap[mod_index * max_mod_keys + key_index];
159      int keysym = XkbKeycodeToKeysym(display, key, 0, 0);
160      if (modifier == kAltKeyModifierMask)
161        found = keysym == XK_Alt_L || keysym == XK_Alt_R;
162      else if (modifier == kMetaKeyModifierMask)
163        found = keysym == XK_Meta_L || keysym == XK_Meta_R;
164      else if (modifier == kNumLockKeyModifierMask)
165        found = keysym == XK_Num_Lock;
166      if (found) {
167        *x_modifier = 1 << mod_index;
168        break;
169      }
170    }
171    if (found)
172      break;
173  }
174  XFreeModifiermap(mod_map);
175  return found;
176}
177
178}  // namespace
179
180bool ConvertKeyCodeToText(
181    ui::KeyboardCode key_code, int modifiers, std::string* text,
182    std::string* error_msg) {
183  *error_msg = std::string();
184  int x_key_code = KeyboardCodeToXKeyCode(key_code);
185  if (x_key_code == -1) {
186    *text = std::string();
187    return true;
188  }
189
190  XEvent event;
191  memset(&event, 0, sizeof(XEvent));
192  XKeyEvent* key_event = &event.xkey;
193  XDisplay* display = gfx::GetXDisplay();
194  if (!display) {
195    *error_msg =
196        "an X display is required for keycode conversions, consider using Xvfb";
197    *text = std::string();
198    return false;
199  }
200  key_event->display = display;
201  key_event->keycode = x_key_code;
202  if (modifiers & kShiftKeyModifierMask)
203    key_event->state |= ShiftMask;
204  if (modifiers & kControlKeyModifierMask)
205    key_event->state |= ControlMask;
206
207  // Make a best attempt for non-standard modifiers.
208  int x_modifier;
209  if (modifiers & kAltKeyModifierMask &&
210      GetXModifierMask(display, kAltKeyModifierMask, &x_modifier)) {
211    key_event->state |= x_modifier;
212  }
213  if (modifiers & kMetaKeyModifierMask &&
214      GetXModifierMask(display, kMetaKeyModifierMask, &x_modifier)) {
215    key_event->state |= x_modifier;
216  }
217  if (modifiers & kNumLockKeyModifierMask &&
218      GetXModifierMask(display, kNumLockKeyModifierMask, &x_modifier)) {
219    key_event->state |= x_modifier;
220  }
221  key_event->type = KeyPress;
222  uint16 character = ui::GetCharacterFromXEvent(&event);
223
224  if (!character)
225    *text = std::string();
226  else
227    *text = base::UTF16ToUTF8(base::string16(1, character));
228  return true;
229}
230
231bool ConvertCharToKeyCode(
232    base::char16 key,
233    ui::KeyboardCode* key_code,
234    int* necessary_modifiers,
235    std::string* error_msg) {
236  std::string key_string(base::UTF16ToUTF8(base::string16(1, key)));
237  bool found = false;
238  ui::KeyboardCode test_code;
239  int test_modifiers;
240  *error_msg = std::string();
241  std::string conv_string;
242  for (size_t i = 0; i < arraysize(kKeyCodeToXKeyCode); ++i) {
243    test_code = kKeyCodeToXKeyCode[i].key_code;
244    // Skip the numpad keys.
245    if (test_code >= ui::VKEY_NUMPAD0 && test_code <= ui::VKEY_DIVIDE)
246      continue;
247    test_modifiers = 0;
248    if (!ConvertKeyCodeToText(
249        test_code, test_modifiers, &conv_string, error_msg))
250      return false;
251    if (conv_string == key_string) {
252      found = true;
253      break;
254    }
255    test_modifiers = kShiftKeyModifierMask;
256    if (!ConvertKeyCodeToText(
257        test_code, test_modifiers, &conv_string, error_msg))
258      return false;
259    if (conv_string == key_string) {
260      found = true;
261      break;
262    }
263  }
264  if (found) {
265    *key_code = test_code;
266    *necessary_modifiers = test_modifiers;
267  }
268  return found;
269}
270