File: keyboard_layout_monitor_win.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (602 lines) | stat: -rw-r--r-- 23,766 bytes parent folder | download | duplicates (6)
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