// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/host/keyboard_layout_monitor.h"
#include <windows.h>
#include <ime.h>
#include <memory>
#include <string_view>
#include <utility>
#include <vector>
#include "base/compiler_specific.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_local.h"
#include "base/timer/timer.h"
#include "remoting/proto/control.pb.h"
#include "third_party/webrtc/modules/desktop_capture/win/desktop.h"
#include "third_party/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
namespace remoting {
namespace {
constexpr base::TimeDelta POLL_INTERVAL = base::Milliseconds(1000);
// If second is equivalent to first (generates the same functions/characters at
// all shift levels), second will be removed from the map.
constexpr std::pair<ui::DomCode, ui::DomCode> POSSIBLE_EQUIVALENTS[] = {
// These are equivalent on the US QWERTY layout, among others.
{ui::DomCode::BACKSLASH, ui::DomCode::INTL_BACKSLASH}};
class KeyboardLayoutMonitorWin : public KeyboardLayoutMonitor {
public:
KeyboardLayoutMonitorWin(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner);
~KeyboardLayoutMonitorWin() override;
void Start() override;
private:
// Check the current layout, and invoke the callback if it has changed.
void QueryLayout();
void ResetTimer();
static void QueryLayoutOnInputThread(
scoped_refptr<base::SequencedTaskRunner> reply_sequence,
base::WeakPtr<KeyboardLayoutMonitorWin> monitor,
HKL previous_layout);
void OnLayoutChanged(HKL new_layout, protocol::KeyboardLayout layout_details);
HKL previous_layout_ = nullptr;
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback_;
base::RetainingOneShotTimer timer_;
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
base::WeakPtrFactory<KeyboardLayoutMonitorWin> weak_ptr_factory_;
};
KeyboardLayoutMonitorWin::KeyboardLayoutMonitorWin(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner)
: callback_(std::move(callback)),
input_task_runner_(std::move(input_task_runner)),
weak_ptr_factory_(this) {}
KeyboardLayoutMonitorWin::~KeyboardLayoutMonitorWin() = default;
void KeyboardLayoutMonitorWin::Start() {
timer_.Start(FROM_HERE, POLL_INTERVAL, this,
&KeyboardLayoutMonitorWin::QueryLayout);
}
void ClearDeadKeys(HKL layout);
bool IsNumpadKey(ui::DomCode code);
UINT TranslateVirtualKey(bool numlock_state,
bool shift_state,
UINT virtual_key,
ui::DomCode code);
protocol::LayoutKeyFunction VirtualKeyToLayoutKeyFunction(UINT virtual_key,
LANGID lang);
void KeyboardLayoutMonitorWin::QueryLayout() {
// Only reset the timer once the task has completed. This ensures that a delay
// on the input thread doesn't result in us queuing up a bunch of redundant
// tasks.
input_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(&QueryLayoutOnInputThread,
base::SequencedTaskRunner::GetCurrentDefault(),
weak_ptr_factory_.GetWeakPtr(), previous_layout_),
base::BindOnce(&KeyboardLayoutMonitorWin::ResetTimer,
weak_ptr_factory_.GetWeakPtr()));
}
void KeyboardLayoutMonitorWin::ResetTimer() {
timer_.Reset();
}
// static
void KeyboardLayoutMonitorWin::QueryLayoutOnInputThread(
scoped_refptr<base::SequencedTaskRunner> reply_sequence,
base::WeakPtr<KeyboardLayoutMonitorWin> monitor,
HKL previous_layout) {
// Switch to the active desktop.
webrtc::ScopedThreadDesktop desktop;
std::unique_ptr<webrtc::Desktop> input_desktop(
webrtc::Desktop::GetInputDesktop());
if (input_desktop && !desktop.IsSame(*input_desktop)) {
desktop.SetThreadDesktop(input_desktop.release());
}
// Get the keyboard layout for the active window.
DWORD thread_id = 0;
HWND foreground_window = GetForegroundWindow();
if (foreground_window) {
thread_id = GetWindowThreadProcessId(foreground_window, nullptr);
} else if (previous_layout != 0) {
// There's no currently active window, so keep using the previous layout.
return;
}
// If there is no previous layout and there's no active window
// (thread_id == 0), this will return the layout associated with this
// thread, which is better than nothing.
HKL layout = GetKeyboardLayout(thread_id);
if (layout == previous_layout) {
return;
}
protocol::KeyboardLayout layout_message;
// TODO(rkjnsn): Windows doesn't provide an API to read the keyboard layout
// directly. We can use the various key translation functions to mostly get
// the information we need, but it requires some hacks, could miss some edge
// cases, and modifies the system keyboard state. Windows keyboard layouts
// consist of DLLs providing data tables in a reasonable straight-forward
// format, so it may make sense to look at reading them directly.
// It would be nice if we could use MapVirtualKeyEx to translate virtual
// keys to characters, as it neither is affected by nor modifies the system
// keyboard state. Unfortunately, it provides no way to specify shift
// states, so it can only be used to retrieve the unshifted character for a
// given key. Instead, we use ToUnicodeEx, which is affected by (and
// affects) the system keyboard state, so we start by clearing any queued-up
// dead keys.
ClearDeadKeys(layout);
// Keyboard layouts have a KLLF_ALTGR flag that indicated whether the right
// alt key is AltGr (and thus generates Ctrl+Alt). Unfortunately, there
// doesn't seem to be any way to determine whether the current layout sets
// this flag using the Windows API. As a hack/workaround, we assume that
// right Alt == AltGr if and only if there are keys that generate characters
// when both Ctrl and Alt modifiers are set. This is by no means guaranteed,
// but is expected to be true for common layouts.
bool has_altgr = false;
// Keys/shift levels that function as right Alt. These will be updated to
// AltGr if the keyboard appears to use it. (Most keyboards will have at
// most one of these.)
std::vector<std::pair<std::uint32_t, int>> right_alts;
for (ui::DomCode key : KeyboardLayoutMonitor::kSupportedKeys) {
// These keys cannot be injected properly on Windows with the APIs we use.
//
// The USB keyboard driver translates NumLock to the scancode 0x45, but it
// is delivered to applications as 0xE045. Meanwhile, Pause is translated
// to the scancode sequence 0xE1 0x1D 0x45 0xE1 0x9D 0xC5, but is delivered
// to applications as a plain 0x45.
//
// Injecting 0x45 using SendInput does get interpreted by Windows as
// VK_NUMLOCK, but is not translated to 0xE045 before being delivered to
// applications as it is with a physical keyboard. As a result, Chrome (for
// example) reports a key value of "NumLock" but a code value of "Pause".
//
// Injecting 0xE045, on the otherhand, does not get interpreted as
// VK_NUMLOCK, so Chrome sees reports a code value of "NumLock", but a key
// value of "Unidentified".
//
// I have not been able to determine any way to use SendInput to inject an
// event that Windows interprets as VK_PAUSE.
//
// NumpadEqual also behaves inconsistently when injected, but in a
// different way: while when input using a physical keyboard, the
// corresponding scancode (0x59) is always interpreted as VK_CLEAR
// regardless of the num lock state. When injected, however, Windows
// translates the key to VK_NUMPAD5 when numlock is enabled. Since the
// virtual keyboard considers num lock always to be enabled, this
// effectively results in an extra 5 key in the NumpadEqual position, which
// is both redundant and confusing. Given that most keyboards lack this key,
// and those that do have it label it '=', it seems easiest just to exclude
// it for now. In the future, we could consider adding support to it for
// keyboard layouts that treat is as something other than VK_CLEAR, if such
// layouts turn out to exist. (Why Windows maps the USB 'Keypad =' key to
// scancode 0x59 in the first place, even though 0x59 does not generate an
// '=' character, is unclear.)
if (key == ui::DomCode::NUM_LOCK || key == ui::DomCode::PAUSE ||
key == ui::DomCode::NUMPAD_EQUAL) {
continue;
}
std::uint32_t usb_code = ui::KeycodeConverter::DomCodeToUsbKeycode(key);
int scancode = ui::KeycodeConverter::DomCodeToNativeKeycode(key);
UINT virtual_key = MapVirtualKeyEx(scancode, MAPVK_VSC_TO_VK_EX, layout);
if (virtual_key == 0) {
// This key is not mapped in the current layout.
continue;
}
if (virtual_key == VK_CAPITAL || virtual_key == VK_NUMLOCK) {
// Don't send caps or numlock keys until we decide how to handle them.
// (We currently skip the key in the NumLock position above due to
// difficulties injecting it, but the user still may have mapped a
// different key to that function.)
continue;
}
google::protobuf::Map<google::protobuf::uint32,
protocol::KeyboardLayout_KeyAction>& key_actions =
*(*layout_message.mutable_keys())[usb_code].mutable_actions();
for (int shift_level = 0; shift_level < 4; ++shift_level) {
// Mimic Windows's handling of number pad key.
UINT translated_key = TranslateVirtualKey(
/* numlock_state */ true, shift_level & 1, virtual_key, key);
// First check if the key generates a character.
BYTE key_state[256] = {0};
// Modifiers set the high-order bit when pressed.
key_state[VK_SHIFT] = (shift_level & 1) << 7;
key_state[VK_CONTROL] = key_state[VK_MENU] = (shift_level & 2) << 6;
// Locks set the low-order bit when toggled on.
// For now, generate a layout with numlock always on and caps lock
// always off.
// TODO(rkjnsn): Update this when we decide how we want to handle locks
// for the on-screen keyboard.
key_state[VK_NUMLOCK] = 1;
key_state[VK_CAPITAL] = 0;
WCHAR char_buffer[16];
// According to the documentation, ToUnicodeEx usually does the
// translation solely based on the virtual key, but can use bit 15 of the
// scancode to distinguish between a keypress and a key release. This
// suggests that the expected format for scancode is the upper word of
// lParam from a WM_CHAR, where the top bit similarly distinguished press
// versus release. In any event, passing |scancode| as the second
// parameter here would thus cause extended scancodes (which have the 15th
// bit set) to erroneously be interpreted as key-up events and not
// generate the appropriate character. Rather than attempting to munge the
// scancode into whatever format ToUnicodeEx expects, passing 0 seems to
// work just fine.
int size = ToUnicodeEx(translated_key, 0, key_state, char_buffer,
std::size(char_buffer), 0, layout);
if (size < 0) {
// We don't handle dead keys specially for the layout, but we do
// need to clear them from the system keyboard state.
ClearDeadKeys(layout);
size = -size;
}
if (size > 0) {
if (size == 1 && char_buffer[0] < 0x20) {
// Handle known control characters.
protocol::LayoutKeyFunction function =
protocol::LayoutKeyFunction::UNKNOWN;
switch (char_buffer[0]) {
case 0x08:
function = protocol::LayoutKeyFunction::BACKSPACE;
break;
case 0x09:
function = protocol::LayoutKeyFunction::TAB;
break;
case 0x0D:
function = protocol::LayoutKeyFunction::ENTER;
break;
case 0x1B:
function = protocol::LayoutKeyFunction::ESCAPE;
break;
}
if (function != protocol::LayoutKeyFunction::UNKNOWN) {
key_actions[shift_level].set_function(function);
continue;
}
}
// The key generated at least one character.
key_actions[shift_level].set_character(
base::WideToUTF8(std::wstring_view(char_buffer, size)));
if (shift_level > 2) {
has_altgr = true;
}
continue;
}
// If the key didn't generate a character, translate it based on the
// virtual key value.
key_actions[shift_level].set_function(VirtualKeyToLayoutKeyFunction(
translated_key, reinterpret_cast<std::uintptr_t>(layout) & 0xFFFF));
if (translated_key == VK_RMENU) {
right_alts.emplace_back(usb_code, shift_level);
}
}
}
// If any ctrl+alt+key sequence generated a character, assume right Alt is
// AltGr.
if (has_altgr) {
for (std::pair<std::uint32_t, int> right_alt : right_alts) {
(*(*layout_message.mutable_keys())[right_alt.first]
.mutable_actions())[right_alt.second]
.set_function(protocol::LayoutKeyFunction::ALT_GR);
}
} else {
// Remove higher shift levels since there's no way to generate them.
for (auto& key : *layout_message.mutable_keys()) {
key.second.mutable_actions()->erase(2);
key.second.mutable_actions()->erase(3);
}
}
// Some layouts have equivalent keys. Remove the redundant keys to make the
// layout cleaner.
auto* keys = layout_message.mutable_keys();
for (const std::pair<ui::DomCode, ui::DomCode>& possible_equivalent :
POSSIBLE_EQUIVALENTS) {
std::uint32_t code1 =
ui::KeycodeConverter::DomCodeToUsbKeycode(possible_equivalent.first);
std::uint32_t code2 =
ui::KeycodeConverter::DomCodeToUsbKeycode(possible_equivalent.second);
auto key_behavior1 = keys->find(code1);
auto key_behavior2 = keys->find(code2);
if (key_behavior1 != keys->end() && key_behavior2 != keys->end() &&
key_behavior1->second.SerializeAsString() ==
key_behavior2->second.SerializeAsString()) {
keys->erase(key_behavior2);
}
}
// There seem to be a number of keys that are mapped to a virtual key in the
// layout but don't do anything useful. E.g., the US QWERTY layout maps
// NonConvert, which isn't on a standard US keyboard, to VK_OEM_PA1, which
// doesn't appear to be useful. To avoid cluttering the on-screen keyboard
// with blank, useless keys, just omit unknown keys for now. We can revisit
// this if folks send feedback about useful keys being missing.
for (auto it = keys->begin(); it != keys->end();) {
bool has_action = false;
for (const auto& action : it->second.actions()) {
if (action.second.has_character() ||
(action.second.has_function() &&
action.second.function() != protocol::LayoutKeyFunction::UNKNOWN)) {
has_action = true;
}
}
if (!has_action) {
it = keys->erase(it);
} else {
++it;
}
}
reply_sequence->PostTask(
FROM_HERE,
base::BindOnce(&KeyboardLayoutMonitorWin::OnLayoutChanged,
std::move(monitor), layout, std::move(layout_message)));
}
void KeyboardLayoutMonitorWin::OnLayoutChanged(
HKL new_layout,
protocol::KeyboardLayout layout_details) {
previous_layout_ = new_layout;
callback_.Run(std::move(layout_details));
}
void ClearDeadKeys(HKL layout) {
// ToUnicodeEx both is affected by and modifies the current keyboard state,
// which includes the list of currently stored dead keys. Pressing space
// translates previously pressed dead keys to characters, clearing the dead-
// key buffer.
BYTE key_state[256] = {0};
WCHAR char_buffer[16];
ToUnicodeEx(VK_SPACE,
ui::KeycodeConverter::DomCodeToNativeKeycode(ui::DomCode::SPACE),
key_state, char_buffer, std::size(char_buffer), 0, layout);
}
bool IsNumpadKey(ui::DomCode code) {
// Windows keyboard layouts map number pad keys to virtual keys based on
// their function when num lock is off. E.g., 4 on the number pad generates
// VK_LEFT, the same as the left arrow key. To distinguish them, the layout
// sets the KBDNUMPAD flag on the numlock versions, allowing Windows to
// translate VK_LEFT to VK_NUMPAD4 when numlock is enabled only for the keys
// on the number pad. Unfortunately, the state of the KBDNUMPAD flag for a
// given scan code does not appear to be accessible via the Windows API, so
// for now just assume that all layouts use the same scan codes for the
// number pad.
// TODO(rkjnsn): Figure out if there's a better way to determine this.
switch (code) {
case ui::DomCode::NUMPAD0:
case ui::DomCode::NUMPAD1:
case ui::DomCode::NUMPAD2:
case ui::DomCode::NUMPAD3:
case ui::DomCode::NUMPAD4:
case ui::DomCode::NUMPAD5:
case ui::DomCode::NUMPAD6:
case ui::DomCode::NUMPAD7:
case ui::DomCode::NUMPAD8:
case ui::DomCode::NUMPAD9:
case ui::DomCode::NUMPAD_DECIMAL:
return true;
default:
return false;
}
}
UINT TranslateVirtualKey(bool numlock_state,
bool shift_state,
UINT virtual_key,
ui::DomCode code) {
// Windows only translates numpad keys when num lock is on and shift is not
// pressed. (Pressing shift when num lock is on will get you navigation, but
// pressing shift when num lock is off will not get you numbers.)
if (!numlock_state || shift_state || !IsNumpadKey(code)) {
return virtual_key;
}
switch (virtual_key) {
case VK_DELETE:
return VK_DECIMAL;
case VK_INSERT:
return VK_NUMPAD0;
case VK_END:
return VK_NUMPAD1;
case VK_DOWN:
return VK_NUMPAD2;
case VK_NEXT:
return VK_NUMPAD3;
case VK_LEFT:
return VK_NUMPAD4;
case VK_CLEAR:
return VK_NUMPAD5;
case VK_RIGHT:
return VK_NUMPAD6;
case VK_HOME:
return VK_NUMPAD7;
case VK_UP:
return VK_NUMPAD8;
case VK_PRIOR:
return VK_NUMPAD9;
default:
return virtual_key;
}
}
protocol::LayoutKeyFunction VirtualKeyToLayoutKeyFunction(UINT virtual_key,
LANGID lang) {
switch (virtual_key) {
case VK_LCONTROL:
case VK_RCONTROL:
return protocol::LayoutKeyFunction::CONTROL;
case VK_LMENU:
case VK_RMENU:
return protocol::LayoutKeyFunction::ALT;
case VK_LSHIFT:
case VK_RSHIFT:
return protocol::LayoutKeyFunction::SHIFT;
case VK_LWIN:
case VK_RWIN:
return protocol::LayoutKeyFunction::META;
case VK_NUMLOCK:
return protocol::LayoutKeyFunction::NUM_LOCK;
case VK_CAPITAL:
return protocol::LayoutKeyFunction::CAPS_LOCK;
case VK_SCROLL:
return protocol::LayoutKeyFunction::SCROLL_LOCK;
case VK_BACK:
return protocol::LayoutKeyFunction::BACKSPACE;
case VK_RETURN:
return protocol::LayoutKeyFunction::ENTER;
case VK_TAB:
return protocol::LayoutKeyFunction::TAB;
case VK_INSERT:
return protocol::LayoutKeyFunction::INSERT;
case VK_DELETE:
return protocol::LayoutKeyFunction::DELETE_;
case VK_HOME:
return protocol::LayoutKeyFunction::HOME;
case VK_END:
return protocol::LayoutKeyFunction::END;
case VK_PRIOR:
return protocol::LayoutKeyFunction::PAGE_UP;
case VK_NEXT:
return protocol::LayoutKeyFunction::PAGE_DOWN;
case VK_CLEAR:
return protocol::LayoutKeyFunction::CLEAR;
case VK_UP:
return protocol::LayoutKeyFunction::ARROW_UP;
case VK_DOWN:
return protocol::LayoutKeyFunction::ARROW_DOWN;
case VK_LEFT:
return protocol::LayoutKeyFunction::ARROW_LEFT;
case VK_RIGHT:
return protocol::LayoutKeyFunction::ARROW_RIGHT;
case VK_F1:
return protocol::LayoutKeyFunction::F1;
case VK_F2:
return protocol::LayoutKeyFunction::F2;
case VK_F3:
return protocol::LayoutKeyFunction::F3;
case VK_F4:
return protocol::LayoutKeyFunction::F4;
case VK_F5:
return protocol::LayoutKeyFunction::F5;
case VK_F6:
return protocol::LayoutKeyFunction::F6;
case VK_F7:
return protocol::LayoutKeyFunction::F7;
case VK_F8:
return protocol::LayoutKeyFunction::F8;
case VK_F9:
return protocol::LayoutKeyFunction::F9;
case VK_F10:
return protocol::LayoutKeyFunction::F10;
case VK_F11:
return protocol::LayoutKeyFunction::F11;
case VK_F12:
return protocol::LayoutKeyFunction::F12;
case VK_F13:
return protocol::LayoutKeyFunction::F13;
case VK_F14:
return protocol::LayoutKeyFunction::F14;
case VK_F15:
return protocol::LayoutKeyFunction::F15;
case VK_F16:
return protocol::LayoutKeyFunction::F16;
case VK_F17:
return protocol::LayoutKeyFunction::F17;
case VK_F18:
return protocol::LayoutKeyFunction::F18;
case VK_F19:
return protocol::LayoutKeyFunction::F19;
case VK_F20:
return protocol::LayoutKeyFunction::F20;
case VK_F21:
return protocol::LayoutKeyFunction::F21;
case VK_F22:
return protocol::LayoutKeyFunction::F22;
case VK_F23:
return protocol::LayoutKeyFunction::F23;
case VK_F24:
return protocol::LayoutKeyFunction::F24;
case VK_ESCAPE:
return protocol::LayoutKeyFunction::ESCAPE;
case VK_APPS:
return protocol::LayoutKeyFunction::CONTEXT_MENU;
case VK_PAUSE:
return protocol::LayoutKeyFunction::PAUSE;
case VK_SNAPSHOT:
return protocol::LayoutKeyFunction::PRINT_SCREEN;
}
// Handle language-specific keys.
if (PRIMARYLANGID(lang) == 0x11) { // Japanese
switch (virtual_key) {
case VK_DBE_SBCSCHAR:
return protocol::LayoutKeyFunction::HANKAKU_ZENKAKU_KANJI;
case VK_CONVERT:
return protocol::LayoutKeyFunction::HENKAN;
case VK_NONCONVERT:
return protocol::LayoutKeyFunction::MUHENKAN;
case VK_DBE_KATAKANA:
case VK_DBE_HIRAGANA:
// TODO(rkjnsn): Make sure it makes sense to use the same key cap for
// both of these.
return protocol::LayoutKeyFunction::KATAKANA_HIRAGANA_ROMAJI;
case VK_DBE_ALPHANUMERIC:
return protocol::LayoutKeyFunction::EISU;
}
} else if (PRIMARYLANGID(lang) == 0x12) { // Korean
switch (virtual_key) {
case VK_HANJA:
return protocol::LayoutKeyFunction::HANJA;
case VK_HANGUL:
return protocol::LayoutKeyFunction::HAN_YEONG;
}
}
return protocol::LayoutKeyFunction::UNKNOWN;
}
} // namespace
std::unique_ptr<KeyboardLayoutMonitor> KeyboardLayoutMonitor::Create(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner) {
return std::make_unique<KeyboardLayoutMonitorWin>(
std::move(callback), std::move(input_task_runner));
}
} // namespace remoting