1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602
|
// 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] = {};
// 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] = {};
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
|