1 // Copyright 2018 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 <windows.h>
6 
7 #include <cstdint>
8 #include <string>
9 #include <utility>
10 #include <vector>
11 
12 #include "base/containers/flat_map.h"
13 #include "base/logging.h"
14 #include "base/macros.h"
15 #include "base/stl_util.h"
16 #include "ui/events/keycodes/dom/dom_code.h"
17 #include "ui/events/keycodes/dom/dom_key.h"
18 #include "ui/events/keycodes/dom/dom_keyboard_layout_map_base.h"
19 #include "ui/events/keycodes/dom/keycode_converter.h"
20 
21 namespace ui {
22 
23 namespace {
24 
25 class DomKeyboardLayoutMapWin : public DomKeyboardLayoutMapBase {
26  public:
27   DomKeyboardLayoutMapWin();
28   ~DomKeyboardLayoutMapWin() override;
29 
30  private:
31   // ui::DomKeyboardLayoutMapBase implementation.
32   uint32_t GetKeyboardLayoutCount() override;
33   ui::DomKey GetDomKeyFromDomCodeForLayout(
34       ui::DomCode dom_code,
35       uint32_t keyboard_layout_index) override;
36 
37   // Set of keyboard layout handles provided by the operating system.
38   // The handles stored do not need to be released when the vector is destroyed.
39   std::vector<HKL> keyboard_layout_handles_;
40 
41   DISALLOW_COPY_AND_ASSIGN(DomKeyboardLayoutMapWin);
42 };
43 
44 DomKeyboardLayoutMapWin::DomKeyboardLayoutMapWin() = default;
45 
46 DomKeyboardLayoutMapWin::~DomKeyboardLayoutMapWin() = default;
47 
GetKeyboardLayoutCount()48 uint32_t DomKeyboardLayoutMapWin::GetKeyboardLayoutCount() {
49   keyboard_layout_handles_.clear();
50   const size_t keyboard_layout_count = ::GetKeyboardLayoutList(0, nullptr);
51   if (!keyboard_layout_count) {
52     DPLOG(ERROR) << "GetKeyboardLayoutList failed: ";
53     return false;
54   }
55 
56   keyboard_layout_handles_.resize(keyboard_layout_count);
57   const size_t copy_count = ::GetKeyboardLayoutList(
58       keyboard_layout_handles_.size(), keyboard_layout_handles_.data());
59   if (!copy_count) {
60     DPLOG(ERROR) << "GetKeyboardLayoutList failed: ";
61     return false;
62   }
63   DCHECK_EQ(keyboard_layout_count, copy_count);
64 
65   // The set of layouts returned from GetKeyboardLayoutList does not follow the
66   // the order of the layouts in the control panel so we use GetKeyboardLayout
67   // to retrieve the current layout and swap (if needed) to ensure it is always
68   // evaluated first.
69   auto iter = std::find(keyboard_layout_handles_.begin(),
70                         keyboard_layout_handles_.end(), GetKeyboardLayout(0));
71   if (iter != keyboard_layout_handles_.begin() &&
72       iter != keyboard_layout_handles_.end())
73     std::iter_swap(keyboard_layout_handles_.begin(), iter);
74 
75   return keyboard_layout_handles_.size();
76 }
77 
GetDomKeyFromDomCodeForLayout(ui::DomCode dom_code,uint32_t keyboard_layout_index)78 ui::DomKey DomKeyboardLayoutMapWin::GetDomKeyFromDomCodeForLayout(
79     ui::DomCode dom_code,
80     uint32_t keyboard_layout_index) {
81   DCHECK_NE(dom_code, ui::DomCode::NONE);
82   DCHECK_LT(keyboard_layout_index, keyboard_layout_handles_.size());
83 
84   HKL keyboard_layout = keyboard_layout_handles_[keyboard_layout_index];
85   int32_t scan_code = ui::KeycodeConverter::DomCodeToNativeKeycode(dom_code);
86   uint32_t virtual_key_code =
87       MapVirtualKeyEx(scan_code, MAPVK_VSC_TO_VK_EX, keyboard_layout);
88   if (!virtual_key_code) {
89     if (GetLastError() != 0)
90       DPLOG(ERROR) << "MapVirtualKeyEx failed: ";
91     return ui::DomKey::NONE;
92   }
93 
94   // Represents a keyboard state with all keys up (i.e. no keys pressed).
95   BYTE keyboard_state[256] = {0};
96 
97   // ToUnicodeEx() return value indicates the category for the scan code
98   // passed in for the keyboard layout provided.
99   // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646322(v=vs.85).aspx
100   wchar_t char_buffer[1] = {0};
101   int key_type =
102       ::ToUnicodeEx(virtual_key_code, scan_code, keyboard_state, char_buffer,
103                     base::size(char_buffer), /*wFlags=*/0, keyboard_layout);
104 
105   // Handle special cases for Japanese keyboard layout.
106   if (0x04110411 == reinterpret_cast<uintptr_t>(keyboard_layout)) {
107     // Fix value for Japanese yen currency symbol.
108     // Windows returns '\' for both IntlRo and IntlYen, even though IntlYen
109     // should be the yen symbol.
110     if (dom_code == ui::DomCode::INTL_YEN)
111       return ui::DomKey::FromCharacter(0x00a5);  // Japanese yen symbol.
112 
113     // Special case for Backquote.
114     // Technically, this layout is not completely ASCII-capable because the
115     // Backquote key is used as an IME function key (hankaku/zenkaku) and is
116     // thus not a printable key. However, other than this key, it is a perfectly
117     // usable ASCII-capable layout and it matches the values printed on the
118     // keyboard, so we have special handling to allow this key.
119     if (dom_code == ui::DomCode::BACKQUOTE)
120       return ui::DomKey::ZENKAKU_HANKAKU;
121   }
122 
123   // Handle special cases for Korean keyboard layout.
124   if (0x04120412 == reinterpret_cast<uintptr_t>(keyboard_layout)) {
125     // Fix value for Korean won currency symbol.
126     // Windows returns '\' for both Backslash and IntlBackslash, even though
127     // IntlBackslash should be the won symbol.
128     if (dom_code == ui::DomCode::INTL_BACKSLASH)
129       return ui::DomKey::FromCharacter(0x20a9);  // Korean won symbol.
130   }
131 
132   ui::DomKey key = ui::DomKey::NONE;
133   if (key_type == 1)
134     key = ui::DomKey::FromCharacter(char_buffer[0]);
135   else if (key_type == -1) {
136     key = ui::DomKey::DeadKeyFromCombiningCharacter(char_buffer[0]);
137 
138     // When we query info about dead keys, the system is left in a state
139     // such that the next key queried is in the context of that dead key.
140     // This causes ToUnicodeEx to return an incorrect result for the second
141     // key. To fix this we query a Space key after any dead key to clear out
142     // the dead key state. See crbug/977609 for details on how this problem
143     // exhibits itself to users.
144     ::ToUnicodeEx(0x0020, 0x0039, keyboard_state, char_buffer,
145                   base::size(char_buffer), /*wFlags=*/0, keyboard_layout);
146   }
147   return key;
148 }
149 
150 }  // namespace
151 
152 // static
GenerateDomKeyboardLayoutMap()153 base::flat_map<std::string, std::string> GenerateDomKeyboardLayoutMap() {
154   return DomKeyboardLayoutMapWin().Generate();
155 }
156 
157 }  // namespace ui
158