// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <windows.h>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/dom/dom_keyboard_layout_map_base.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace ui {
namespace {
class DomKeyboardLayoutMapWin : public DomKeyboardLayoutMapBase {
public:
DomKeyboardLayoutMapWin();
DomKeyboardLayoutMapWin(const DomKeyboardLayoutMapWin&) = delete;
DomKeyboardLayoutMapWin& operator=(const DomKeyboardLayoutMapWin&) = delete;
~DomKeyboardLayoutMapWin() override;
private:
// ui::DomKeyboardLayoutMapBase implementation.
uint32_t GetKeyboardLayoutCount() override;
ui::DomKey GetDomKeyFromDomCodeForLayout(
ui::DomCode dom_code,
uint32_t keyboard_layout_index) override;
// Set of keyboard layout handles provided by the operating system.
// The handles stored do not need to be released when the vector is destroyed.
std::vector<HKL> keyboard_layout_handles_;
};
DomKeyboardLayoutMapWin::DomKeyboardLayoutMapWin() = default;
DomKeyboardLayoutMapWin::~DomKeyboardLayoutMapWin() = default;
uint32_t DomKeyboardLayoutMapWin::GetKeyboardLayoutCount() {
keyboard_layout_handles_.clear();
const size_t keyboard_layout_count = ::GetKeyboardLayoutList(0, nullptr);
if (!keyboard_layout_count) {
DPLOG(ERROR) << "GetKeyboardLayoutList failed: ";
return false;
}
keyboard_layout_handles_.resize(keyboard_layout_count);
const size_t copy_count = ::GetKeyboardLayoutList(
keyboard_layout_handles_.size(), keyboard_layout_handles_.data());
if (!copy_count) {
DPLOG(ERROR) << "GetKeyboardLayoutList failed: ";
return false;
}
DCHECK_EQ(keyboard_layout_count, copy_count);
// The set of layouts returned from GetKeyboardLayoutList does not follow the
// the order of the layouts in the control panel so we use GetKeyboardLayout
// to retrieve the current layout and swap (if needed) to ensure it is always
// evaluated first.
auto iter =
base::ranges::find(keyboard_layout_handles_, GetKeyboardLayout(0));
if (iter != keyboard_layout_handles_.begin() &&
iter != keyboard_layout_handles_.end())
std::iter_swap(keyboard_layout_handles_.begin(), iter);
return keyboard_layout_handles_.size();
}
ui::DomKey DomKeyboardLayoutMapWin::GetDomKeyFromDomCodeForLayout(
ui::DomCode dom_code,
uint32_t keyboard_layout_index) {
DCHECK_NE(dom_code, ui::DomCode::NONE);
DCHECK_LT(keyboard_layout_index, keyboard_layout_handles_.size());
HKL keyboard_layout = keyboard_layout_handles_[keyboard_layout_index];
int32_t scan_code = ui::KeycodeConverter::DomCodeToNativeKeycode(dom_code);
uint32_t virtual_key_code =
MapVirtualKeyEx(scan_code, MAPVK_VSC_TO_VK_EX, keyboard_layout);
if (!virtual_key_code) {
if (GetLastError() != 0)
DPLOG(ERROR) << "MapVirtualKeyEx failed: ";
return ui::DomKey::NONE;
}
// Represents a keyboard state with all keys up (i.e. no keys pressed).
BYTE keyboard_state[256] = {0};
// ToUnicodeEx() return value indicates the category for the scan code
// passed in for the keyboard layout provided.
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms646322(v=vs.85).aspx
wchar_t char_buffer[1] = {0};
int key_type =
::ToUnicodeEx(virtual_key_code, scan_code, keyboard_state, char_buffer,
std::size(char_buffer), /*wFlags=*/0, keyboard_layout);
// Handle special cases for Japanese keyboard layout.
if (0x04110411 == reinterpret_cast<uintptr_t>(keyboard_layout)) {
// Fix value for Japanese yen currency symbol.
// Windows returns '\' for both IntlRo and IntlYen, even though IntlYen
// should be the yen symbol.
if (dom_code == ui::DomCode::INTL_YEN)
return ui::DomKey::FromCharacter(0x00a5); // Japanese yen symbol.
// Special case for Backquote.
// Technically, this layout is not completely ASCII-capable because the
// Backquote key is used as an IME function key (hankaku/zenkaku) and is
// thus not a printable key. However, other than this key, it is a perfectly
// usable ASCII-capable layout and it matches the values printed on the
// keyboard, so we have special handling to allow this key.
if (dom_code == ui::DomCode::BACKQUOTE)
return ui::DomKey::ZENKAKU_HANKAKU;
}
// Handle special cases for Korean keyboard layout.
if (0x04120412 == reinterpret_cast<uintptr_t>(keyboard_layout)) {
// Fix value for Korean won currency symbol.
// Windows returns '\' for both Backslash and IntlBackslash, even though
// IntlBackslash should be the won symbol.
if (dom_code == ui::DomCode::INTL_BACKSLASH)
return ui::DomKey::FromCharacter(0x20a9); // Korean won symbol.
}
ui::DomKey key = ui::DomKey::NONE;
if (key_type == 1)
key = ui::DomKey::FromCharacter(char_buffer[0]);
else if (key_type == -1) {
key = ui::DomKey::DeadKeyFromCombiningCharacter(char_buffer[0]);
// When we query info about dead keys, the system is left in a state
// such that the next key queried is in the context of that dead key.
// This causes ToUnicodeEx to return an incorrect result for the second
// key. To fix this we query a Space key after any dead key to clear out
// the dead key state. See crbug/977609 for details on how this problem
// exhibits itself to users.
::ToUnicodeEx(0x0020, 0x0039, keyboard_state, char_buffer,
std::size(char_buffer), /*wFlags=*/0, keyboard_layout);
}
return key;
}
} // namespace
// static
base::flat_map<std::string, std::string> GenerateDomKeyboardLayoutMap() {
return DomKeyboardLayoutMapWin().Generate();
}
} // namespace ui