1 // Copyright 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 "ui/events/keycodes/dom/keycode_converter.h"
6 
7 #include "base/logging.h"
8 #include "base/stl_util.h"
9 #include "base/strings/utf_string_conversion_utils.h"
10 #include "build/build_config.h"
11 #include "ui/events/keycodes/dom/dom_code.h"
12 #include "ui/events/keycodes/dom/dom_key.h"
13 
14 namespace ui {
15 
16 namespace {
17 
18 // Table of USB codes (equivalent to DomCode values), native scan codes,
19 // and DOM Level 3 |code| strings.
20 #if defined(OS_WIN)
21 #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
22   { usb, win, code }
23 #elif defined(OS_LINUX) || defined(OS_BSD)
24 #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
25   { usb, xkb, code }
26 #elif defined(OS_MACOSX)
27 #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
28   { usb, mac, code }
29 #elif defined(OS_ANDROID)
30 #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
31   { usb, evdev, code }
32 #elif defined(OS_FUCHSIA)
33 // TODO(https://bugs.fuchsia.com/23982): Fuchsia currently delivers events
34 // with a USB Code but no Page specified, so only map |native_keycode| for
35 // Keyboard Usage Page codes, for now.
36 inline constexpr uint32_t CodeIfOnKeyboardPage(uint32_t usage) {
37   constexpr uint32_t kUsbHidKeyboardPageBase = 0x070000;
38   if ((usage & 0xffff0000) == kUsbHidKeyboardPageBase)
39     return usage & 0xffff;
40   return 0;
41 }
42 #define DOM_CODE(usb, evdev, xkb, win, mac, code, id) \
43   { usb, CodeIfOnKeyboardPage(usb), code }
44 #else
45 #error Unsupported platform
46 #endif
47 #define DOM_CODE_DECLARATION const KeycodeMapEntry kDomCodeMappings[] =
48 #include "ui/events/keycodes/dom/dom_code_data.inc"
49 #undef DOM_CODE
50 #undef DOM_CODE_DECLARATION
51 
52 // Table of DomKey enum values and DOM Level 3 |key| strings.
53 struct DomKeyMapEntry {
54   DomKey dom_key;
55   const char* string;
56 };
57 
58 #define DOM_KEY_MAP_DECLARATION const DomKeyMapEntry kDomKeyMappings[] =
59 #define DOM_KEY_UNI(key, id, value) {DomKey::id, key}
60 #define DOM_KEY_MAP(key, id, value) {DomKey::id, key}
61 #include "ui/events/keycodes/dom/dom_key_data.inc"
62 #undef DOM_KEY_MAP_DECLARATION
63 #undef DOM_KEY_MAP
64 #undef DOM_KEY_UNI
65 
66 }  // namespace
67 
68 // static
NumKeycodeMapEntriesForTest()69 size_t KeycodeConverter::NumKeycodeMapEntriesForTest() {
70   return base::size(kDomCodeMappings);
71 }
72 
73 // static
GetKeycodeMapForTest()74 const KeycodeMapEntry* KeycodeConverter::GetKeycodeMapForTest() {
75   return &kDomCodeMappings[0];
76 }
77 
78 // static
DomKeyStringForTest(size_t index)79 const char* KeycodeConverter::DomKeyStringForTest(size_t index) {
80   if (index >= base::size(kDomKeyMappings))
81     return nullptr;
82   return kDomKeyMappings[index].string;
83 }
84 
85 // static
InvalidNativeKeycode()86 int KeycodeConverter::InvalidNativeKeycode() {
87   return kDomCodeMappings[0].native_keycode;
88 }
89 
90 // TODO(zijiehe): Most of the following functions can be optimized by using
91 // either multiple arrays or unordered_map.
92 
93 // static
NativeKeycodeToDomCode(int native_keycode)94 DomCode KeycodeConverter::NativeKeycodeToDomCode(int native_keycode) {
95   for (auto& mapping : kDomCodeMappings) {
96     if (mapping.native_keycode == native_keycode)
97       return static_cast<DomCode>(mapping.usb_keycode);
98   }
99   return DomCode::NONE;
100 }
101 
102 // static
DomCodeToNativeKeycode(DomCode code)103 int KeycodeConverter::DomCodeToNativeKeycode(DomCode code) {
104   return UsbKeycodeToNativeKeycode(static_cast<uint32_t>(code));
105 }
106 
107 // static
CodeStringToDomCode(const std::string & code)108 DomCode KeycodeConverter::CodeStringToDomCode(const std::string& code) {
109   if (code.empty())
110     return DomCode::NONE;
111   for (auto& mapping : kDomCodeMappings) {
112     if (mapping.code && code == mapping.code) {
113       return static_cast<DomCode>(mapping.usb_keycode);
114     }
115   }
116   LOG(WARNING) << "unrecognized code string '" << code << "'";
117   return DomCode::NONE;
118 }
119 
120 // static
DomCodeToCodeString(DomCode dom_code)121 const char* KeycodeConverter::DomCodeToCodeString(DomCode dom_code) {
122   for (auto& mapping : kDomCodeMappings) {
123     if (mapping.usb_keycode == static_cast<uint32_t>(dom_code)) {
124       if (mapping.code)
125         return mapping.code;
126       break;
127     }
128   }
129   return "";
130 }
131 
132 // static
DomCodeToLocation(DomCode dom_code)133 DomKeyLocation KeycodeConverter::DomCodeToLocation(DomCode dom_code) {
134   static const struct {
135     DomCode code;
136     DomKeyLocation location;
137   } kLocations[] = {{DomCode::CONTROL_LEFT, DomKeyLocation::LEFT},
138                     {DomCode::SHIFT_LEFT, DomKeyLocation::LEFT},
139                     {DomCode::ALT_LEFT, DomKeyLocation::LEFT},
140                     {DomCode::META_LEFT, DomKeyLocation::LEFT},
141                     {DomCode::CONTROL_RIGHT, DomKeyLocation::RIGHT},
142                     {DomCode::SHIFT_RIGHT, DomKeyLocation::RIGHT},
143                     {DomCode::ALT_RIGHT, DomKeyLocation::RIGHT},
144                     {DomCode::META_RIGHT, DomKeyLocation::RIGHT},
145                     {DomCode::NUMPAD_DIVIDE, DomKeyLocation::NUMPAD},
146                     {DomCode::NUMPAD_MULTIPLY, DomKeyLocation::NUMPAD},
147                     {DomCode::NUMPAD_SUBTRACT, DomKeyLocation::NUMPAD},
148                     {DomCode::NUMPAD_ADD, DomKeyLocation::NUMPAD},
149                     {DomCode::NUMPAD_ENTER, DomKeyLocation::NUMPAD},
150                     {DomCode::NUMPAD1, DomKeyLocation::NUMPAD},
151                     {DomCode::NUMPAD2, DomKeyLocation::NUMPAD},
152                     {DomCode::NUMPAD3, DomKeyLocation::NUMPAD},
153                     {DomCode::NUMPAD4, DomKeyLocation::NUMPAD},
154                     {DomCode::NUMPAD5, DomKeyLocation::NUMPAD},
155                     {DomCode::NUMPAD6, DomKeyLocation::NUMPAD},
156                     {DomCode::NUMPAD7, DomKeyLocation::NUMPAD},
157                     {DomCode::NUMPAD8, DomKeyLocation::NUMPAD},
158                     {DomCode::NUMPAD9, DomKeyLocation::NUMPAD},
159                     {DomCode::NUMPAD0, DomKeyLocation::NUMPAD},
160                     {DomCode::NUMPAD_DECIMAL, DomKeyLocation::NUMPAD},
161                     {DomCode::NUMPAD_EQUAL, DomKeyLocation::NUMPAD},
162                     {DomCode::NUMPAD_COMMA, DomKeyLocation::NUMPAD},
163                     {DomCode::NUMPAD_PAREN_LEFT, DomKeyLocation::NUMPAD},
164                     {DomCode::NUMPAD_PAREN_RIGHT, DomKeyLocation::NUMPAD},
165                     {DomCode::NUMPAD_BACKSPACE, DomKeyLocation::NUMPAD},
166                     {DomCode::NUMPAD_MEMORY_STORE, DomKeyLocation::NUMPAD},
167                     {DomCode::NUMPAD_MEMORY_RECALL, DomKeyLocation::NUMPAD},
168                     {DomCode::NUMPAD_MEMORY_CLEAR, DomKeyLocation::NUMPAD},
169                     {DomCode::NUMPAD_MEMORY_ADD, DomKeyLocation::NUMPAD},
170                     {DomCode::NUMPAD_MEMORY_SUBTRACT, DomKeyLocation::NUMPAD},
171                     {DomCode::NUMPAD_SIGN_CHANGE, DomKeyLocation::NUMPAD},
172                     {DomCode::NUMPAD_CLEAR, DomKeyLocation::NUMPAD},
173                     {DomCode::NUMPAD_CLEAR_ENTRY, DomKeyLocation::NUMPAD}};
174   for (const auto& key : kLocations) {
175     if (key.code == dom_code)
176       return key.location;
177   }
178   return DomKeyLocation::STANDARD;
179 }
180 
181 // static
KeyStringToDomKey(const std::string & key)182 DomKey KeycodeConverter::KeyStringToDomKey(const std::string& key) {
183   if (key.empty())
184     return DomKey::NONE;
185   // Check for standard key names.
186   for (auto& mapping : kDomKeyMappings) {
187     if (mapping.string && key == mapping.string) {
188       return mapping.dom_key;
189     }
190   }
191   if (key == "Dead") {
192     // The web KeyboardEvent string does not encode the combining character,
193     // so we just set it to the Unicode designated non-character 0xFFFF.
194     // This will round-trip convert back to 'Dead' but take no part in
195     // character composition.
196     return DomKey::DeadKeyFromCombiningCharacter(0xFFFF);
197   }
198   // Otherwise, if the string contains a single Unicode character,
199   // the key value is that character.
200   int32_t char_index = 0;
201   uint32_t character;
202   if (base::ReadUnicodeCharacter(key.c_str(),
203                                  static_cast<int32_t>(key.length()),
204                                  &char_index, &character) &&
205       key[++char_index] == 0) {
206     return DomKey::FromCharacter(character);
207   }
208   return DomKey::NONE;
209 }
210 
211 // static
DomKeyToKeyString(DomKey dom_key)212 std::string KeycodeConverter::DomKeyToKeyString(DomKey dom_key) {
213   if (dom_key.IsDeadKey()) {
214     // All dead-key combining codes collapse to 'Dead', as UI Events
215     // KeyboardEvent represents the combining character separately.
216     return "Dead";
217   }
218   for (auto& mapping : kDomKeyMappings) {
219     if (mapping.dom_key == dom_key) {
220       if (mapping.string)
221         return mapping.string;
222       break;
223     }
224   }
225   if (dom_key.IsCharacter()) {
226     std::string s;
227     base::WriteUnicodeCharacter(dom_key.ToCharacter(), &s);
228     return s;
229   }
230   return std::string();
231 }
232 
233 // static
IsDomKeyForModifier(DomKey dom_key)234 bool KeycodeConverter::IsDomKeyForModifier(DomKey dom_key) {
235   switch (dom_key) {
236     case DomKey::ACCEL:
237     case DomKey::ALT:
238     case DomKey::ALT_GRAPH:
239     case DomKey::CAPS_LOCK:
240     case DomKey::CONTROL:
241     case DomKey::FN:
242     case DomKey::FN_LOCK:
243     case DomKey::HYPER:
244     case DomKey::META:
245     case DomKey::NUM_LOCK:
246     case DomKey::SCROLL_LOCK:
247     case DomKey::SHIFT:
248     case DomKey::SUPER:
249     case DomKey::SYMBOL:
250     case DomKey::SYMBOL_LOCK:
251     case DomKey::SHIFT_LEVEL5:
252       return true;
253     default:
254       return false;
255   }
256 }
257 
258 // USB keycodes
259 // Note that USB keycodes are not part of any web standard.
260 // Please don't use USB keycodes in new code.
261 
262 // static
InvalidUsbKeycode()263 uint32_t KeycodeConverter::InvalidUsbKeycode() {
264   return kDomCodeMappings[0].usb_keycode;
265 }
266 
267 // static
UsbKeycodeToNativeKeycode(uint32_t usb_keycode)268 int KeycodeConverter::UsbKeycodeToNativeKeycode(uint32_t usb_keycode) {
269   // Deal with some special-cases that don't fit the 1:1 mapping.
270   if (usb_keycode == 0x070032)  // non-US hash.
271     usb_keycode = 0x070031;     // US backslash.
272 #if defined(OS_MACOSX)
273   if (usb_keycode == 0x070046) // PrintScreen.
274     usb_keycode = 0x070068; // F13.
275 #endif
276 
277   for (auto& mapping : kDomCodeMappings) {
278     if (mapping.usb_keycode == usb_keycode)
279       return mapping.native_keycode;
280   }
281   return InvalidNativeKeycode();
282 }
283 
284 // static
NativeKeycodeToUsbKeycode(int native_keycode)285 uint32_t KeycodeConverter::NativeKeycodeToUsbKeycode(int native_keycode) {
286   for (auto& mapping : kDomCodeMappings) {
287     if (mapping.native_keycode == native_keycode)
288       return mapping.usb_keycode;
289   }
290   return InvalidUsbKeycode();
291 }
292 
293 // static
UsbKeycodeToDomCode(uint32_t usb_keycode)294 DomCode KeycodeConverter::UsbKeycodeToDomCode(uint32_t usb_keycode) {
295   for (auto& mapping : kDomCodeMappings) {
296     if (mapping.usb_keycode == usb_keycode)
297       return static_cast<DomCode>(usb_keycode);
298   }
299   return DomCode::NONE;
300 }
301 
302 // static
DomCodeToUsbKeycode(DomCode dom_code)303 uint32_t KeycodeConverter::DomCodeToUsbKeycode(DomCode dom_code) {
304   for (auto& mapping : kDomCodeMappings) {
305     if (mapping.usb_keycode == static_cast<uint32_t>(dom_code))
306       return mapping.usb_keycode;
307   }
308   return InvalidUsbKeycode();
309 }
310 
311 // static
CodeStringToUsbKeycode(const std::string & code)312 uint32_t KeycodeConverter::CodeStringToUsbKeycode(const std::string& code) {
313   if (code.empty())
314     return InvalidUsbKeycode();
315 
316   for (auto& mapping : kDomCodeMappings) {
317     if (mapping.code && code == mapping.code) {
318       return mapping.usb_keycode;
319     }
320   }
321   return InvalidUsbKeycode();
322 }
323 
324 // static
CodeStringToNativeKeycode(const std::string & code)325 int KeycodeConverter::CodeStringToNativeKeycode(const std::string& code) {
326   return UsbKeycodeToNativeKeycode(CodeStringToUsbKeycode(code));
327 }
328 
329 }  // namespace ui
330