blob: 52a44d55838d9c301c8d8caa13ad91f7e55fed1c [file] [log] [blame]
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/events/keycodes/platform_key_map_win.h"
#include <utility>
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/threading/thread_local_storage.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace ui {
namespace {
struct DomCodeEntry {
DomCode dom_code;
int scan_code;
};
#define USB_KEYMAP_DECLARATION const DomCodeEntry supported_dom_code_list[] =
#define USB_KEYMAP(usb, evdev, xkb, win, mac, code, id) {DomCode::id, win}
#include "ui/events/keycodes/dom/keycode_converter_data.inc"
#undef USB_KEYMAP
#undef USB_KEYMAP_DECLARATION
// List of modifiers mentioned in https://w3c.github.io/uievents/#keys-modifiers
// Some modifiers are commented out because they usually don't change keys.
const EventFlags modifier_flags[] = {
EF_SHIFT_DOWN,
EF_CONTROL_DOWN,
EF_ALT_DOWN,
// EF_COMMAND_DOWN,
EF_ALTGR_DOWN,
// EF_NUM_LOCK_ON,
EF_CAPS_LOCK_ON,
// EF_SCROLL_LOCK_ON
};
const int kModifierFlagsCombinations = (1 << arraysize(modifier_flags)) - 1;
int GetModifierFlags(int combination) {
int flags = EF_NONE;
for (size_t i = 0; i < arraysize(modifier_flags); ++i) {
if (combination & (1 << i))
flags |= modifier_flags[i];
}
return flags;
}
void SetModifierState(BYTE* keyboard_state, int flags) {
// According to MSDN GetKeyState():
// 1. If the high-order bit is 1, the key is down; otherwise, it is up.
// 2. If the low-order bit is 1, the key is toggled. A key, such as the
// CAPS LOCK key, is toggled if it is turned on. The key is off and
// untoggled if the low-order bit is 0.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646301.aspx
if (flags & EF_SHIFT_DOWN)
keyboard_state[VK_SHIFT] |= 0x80;
if (flags & EF_CONTROL_DOWN)
keyboard_state[VK_CONTROL] |= 0x80;
if (flags & EF_ALT_DOWN)
keyboard_state[VK_MENU] |= 0x80;
if (flags & EF_ALTGR_DOWN) {
// AltGr should be RightAlt+LeftControl within Windows, but actually only
// the non-located keys will work here.
keyboard_state[VK_MENU] |= 0x80;
keyboard_state[VK_CONTROL] |= 0x80;
}
if (flags & EF_COMMAND_DOWN)
keyboard_state[VK_LWIN] |= 0x80;
if (flags & EF_NUM_LOCK_ON)
keyboard_state[VK_NUMLOCK] |= 0x01;
if (flags & EF_CAPS_LOCK_ON)
keyboard_state[VK_CAPITAL] |= 0x01;
if (flags & EF_SCROLL_LOCK_ON)
keyboard_state[VK_SCROLL] |= 0x01;
}
void CleanupKeyMapTls(void* data) {
PlatformKeyMap* key_map = reinterpret_cast<PlatformKeyMap*>(data);
delete key_map;
}
struct PlatformKeyMapInstanceTlsTraits
: public base::DefaultLazyInstanceTraits<base::ThreadLocalStorage::Slot> {
static base::ThreadLocalStorage::Slot* New(void* instance) {
// Use placement new to initialize our instance in our preallocated space.
// TODO(chongz): Use std::default_delete instead of providing own function.
return new (instance) base::ThreadLocalStorage::Slot(CleanupKeyMapTls);
}
};
base::LazyInstance<base::ThreadLocalStorage::Slot,
PlatformKeyMapInstanceTlsTraits>
g_platform_key_map_tls_lazy = LAZY_INSTANCE_INITIALIZER;
} // anonymous namespace
PlatformKeyMap::PlatformKeyMap() {}
PlatformKeyMap::PlatformKeyMap(HKL layout) {
UpdateLayout(layout);
}
PlatformKeyMap::~PlatformKeyMap() {}
DomKey PlatformKeyMap::DomCodeAndFlagsToDomKey(DomCode code, int flags) const {
const int flags_to_try[] = {
// Trying to match Firefox's behavior and UIEvents DomKey guidelines.
// If the combination doesn't produce a printable character, the key value
// should be the key with no modifiers except for Shift and AltGr.
// See https://w3c.github.io/uievents/#keys-guidelines
flags,
flags & (EF_SHIFT_DOWN | EF_ALTGR_DOWN | EF_CAPS_LOCK_ON),
flags & (EF_SHIFT_DOWN | EF_CAPS_LOCK_ON),
EF_NONE,
};
DomKey key = DomKey::NONE;
for (auto try_flags : flags_to_try) {
const auto& it = code_to_key_.find(std::make_pair(static_cast<int>(code),
try_flags));
if (it != code_to_key_.end()) {
key = it->second;
if (key != DomKey::NONE)
break;
}
}
return key;
}
// static
DomKey PlatformKeyMap::DomCodeAndFlagsToDomKeyStatic(DomCode code, int flags) {
// Use TLS because KeyboardLayout is per thread.
// However currently PlatformKeyMap will only be used by the host application,
// which is just one process and one thread.
base::ThreadLocalStorage::Slot* platform_key_map_tls =
g_platform_key_map_tls_lazy.Pointer();
PlatformKeyMap* platform_key_map =
reinterpret_cast<PlatformKeyMap*>(platform_key_map_tls->Get());
if (!platform_key_map) {
platform_key_map = new PlatformKeyMap();
platform_key_map_tls->Set(platform_key_map);
}
HKL current_layout = ::GetKeyboardLayout(0);
platform_key_map->UpdateLayout(current_layout);
return platform_key_map->DomCodeAndFlagsToDomKey(code, flags);
}
void PlatformKeyMap::UpdateLayout(HKL layout) {
if (layout == keyboard_layout_)
return;
// TODO(chongz): Optimize layout switching (see crbug.com/587147).
keyboard_layout_ = layout;
code_to_key_.clear();
// Map size for some sample keyboard layouts:
// US: 428, French: 554, Persian: 434, Vietnamese: 1388
code_to_key_.reserve(500);
BYTE keyboard_state_to_restore[256];
::GetKeyboardState(keyboard_state_to_restore);
for (int eindex = 0; eindex <= kModifierFlagsCombinations; ++eindex) {
BYTE keyboard_state[256];
memset(keyboard_state, 0, sizeof(keyboard_state));
int flags = GetModifierFlags(eindex);
SetModifierState(keyboard_state, flags);
for (const auto& dom_code_entry : supported_dom_code_list) {
wchar_t translated_chars[5];
int key_code = ::MapVirtualKeyEx(dom_code_entry.scan_code,
MAPVK_VSC_TO_VK, keyboard_layout_);
int rv = ::ToUnicodeEx(key_code, 0, keyboard_state, translated_chars,
arraysize(translated_chars), 0, keyboard_layout_);
if (rv == -1) {
// Dead key, injecting VK_SPACE to get character representation.
BYTE empty_state[256];
memset(empty_state, 0, sizeof(empty_state));
rv = ::ToUnicodeEx(VK_SPACE, 0, empty_state, translated_chars,
arraysize(translated_chars), 0, keyboard_layout_);
// Expecting a dead key character (not followed by a space).
if (rv == 1) {
code_to_key_[std::make_pair(static_cast<int>(dom_code_entry.dom_code),
flags)] =
DomKey::DeadKeyFromCombiningCharacter(translated_chars[0]);
} else {
// TODO(chongz): Check if this will actually happen.
}
} else if (rv == 1) {
if (translated_chars[0] >= 0x20) {
code_to_key_[std::make_pair(static_cast<int>(dom_code_entry.dom_code),
flags)] =
DomKey::FromCharacter(translated_chars[0]);
} else {
// Ignores legacy non-printable control characters.
}
} else {
// TODO(chongz): Handle rv <= -2 and rv >= 2.
}
}
}
::SetKeyboardState(keyboard_state_to_restore);
}
} // namespace ui