text: // Copyright 2019 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 "remoting/host/keyboard_layout_monitor.h"
  
  #include <windows.h>
  #include <ime.h>
  
  #include <memory>
  #include <utility>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/compiler_specific.h"
  #include "base/memory/ptr_util.h"
  #include "base/memory/weak_ptr.h"
  #include "base/no_destructor.h"
  #include "base/single_thread_task_runner.h"
  #include "base/stl_util.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/task/post_task.h"
  #include "base/threading/sequenced_task_runner_handle.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::TimeDelta::FromMilliseconds(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::SequencedTaskRunnerHandle::Get(),
                       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,
                               base::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::UTF16ToUTF8(base::StringPiece16(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, base::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
  