#include "remoting/host/keyboard_layout_monitor.h"
#include <windows.h>
#include <ime.h>
#include <memory>
#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);
constexpr std::pair<ui::DomCode, ui::DomCode> POSSIBLE_EQUIVALENTS[] = {
{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:
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() {
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();
}
void KeyboardLayoutMonitorWin::QueryLayoutOnInputThread(
scoped_refptr<base::SequencedTaskRunner> reply_sequence,
base::WeakPtr<KeyboardLayoutMonitorWin> monitor,
HKL previous_layout) {
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());
}
DWORD thread_id = 0;
HWND foreground_window = GetForegroundWindow();
if (foreground_window) {
thread_id = GetWindowThreadProcessId(foreground_window, nullptr);
} else if (previous_layout != 0) {
return;
}
HKL layout = GetKeyboardLayout(thread_id);
if (layout == previous_layout) {
return;
}
protocol::KeyboardLayout layout_message;
ClearDeadKeys(layout);
bool has_altgr = false;
std::vector<std::pair<std::uint32_t, int>> right_alts;
for (ui::DomCode key : KeyboardLayoutMonitor::kSupportedKeys) {
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) {
continue;
}
if (virtual_key == VK_CAPITAL || virtual_key == VK_NUMLOCK) {
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) {
UINT translated_key = TranslateVirtualKey(
true, shift_level & 1, virtual_key, key);
BYTE key_state[256] = {0};
key_state[VK_SHIFT] = (shift_level & 1) << 7;
key_state[VK_CONTROL] = key_state[VK_MENU] = (shift_level & 2) << 6;
key_state[VK_NUMLOCK] = 1;
key_state[VK_CAPITAL] = 0;
WCHAR char_buffer[16];
int size = ToUnicodeEx(translated_key, 0, key_state, char_buffer,
std::size(char_buffer), 0, layout);
if (size < 0) {
ClearDeadKeys(layout);
size = -size;
}
if (size > 0) {
if (size == 1 && char_buffer[0] < 0x20) {
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;
}
}
key_actions[shift_level].set_character(
base::WideToUTF8(base::WStringPiece(char_buffer, size)));
if (shift_level > 2) {
has_altgr = true;
}
continue;
}
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 (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 {
for (auto& key : *layout_message.mutable_keys()) {
key.second.mutable_actions()->erase(2);
key.second.mutable_actions()->erase(3);
}
}
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);
}
}
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) {
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) {
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) {
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;
}
if (PRIMARYLANGID(lang) == 0x11) {
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:
return protocol::LayoutKeyFunction::KATAKANA_HIRAGANA_ROMAJI;
case VK_DBE_ALPHANUMERIC:
return protocol::LayoutKeyFunction::EISU;
}
} else if (PRIMARYLANGID(lang) == 0x12) {
switch (virtual_key) {
case VK_HANJA:
return protocol::LayoutKeyFunction::HANJA;
case VK_HANGUL:
return protocol::LayoutKeyFunction::HAN_YEONG;
}
}
return protocol::LayoutKeyFunction::UNKNOWN;
}
}
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));
}
}