chromium/remoting/host/input_monitor/local_keyboard_input_monitor_win.cc

// Copyright 2018 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/input_monitor/local_keyboard_input_monitor.h"

#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
#include "remoting/host/input_monitor/local_input_monitor_win.h"
#include "ui/events/keycodes/dom/keycode_converter.h"

namespace remoting {

namespace {

// From the HID Usage Tables specification.
const USHORT kGenericDesktopPage = 1;
const USHORT kKeyboardUsage = 6;

class KeyboardRawInputHandlerWin
    : public LocalInputMonitorWin::RawInputHandler {
 public:
  KeyboardRawInputHandlerWin(
      scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
      scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
      LocalInputMonitor::KeyPressedCallback on_key_event_callback,
      base::OnceClosure disconnect_callback);

  KeyboardRawInputHandlerWin(const KeyboardRawInputHandlerWin&) = delete;
  KeyboardRawInputHandlerWin& operator=(const KeyboardRawInputHandlerWin&) =
      delete;

  ~KeyboardRawInputHandlerWin() override;

  // LocalInputMonitorWin::RawInputHandler implementation.
  bool Register(HWND hwnd) override;
  void Unregister() override;
  void OnInputEvent(const RAWINPUT* input) override;
  void OnError() override;

  scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
  scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;

  LocalInputMonitor::KeyPressedCallback on_key_event_callback_;
  base::OnceClosure disconnect_callback_;

  // Tracks whether the instance is registered to receive raw input events.
  bool registered_ = false;
};

KeyboardRawInputHandlerWin::KeyboardRawInputHandlerWin(
    scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
    LocalInputMonitor::KeyPressedCallback on_key_event_callback,
    base::OnceClosure disconnect_callback)
    : caller_task_runner_(caller_task_runner),
      ui_task_runner_(ui_task_runner),
      on_key_event_callback_(std::move(on_key_event_callback)),
      disconnect_callback_(std::move(disconnect_callback)) {}

KeyboardRawInputHandlerWin::~KeyboardRawInputHandlerWin() {
  DCHECK(!registered_);
}

bool KeyboardRawInputHandlerWin::Register(HWND hwnd) {
  DCHECK(ui_task_runner_->BelongsToCurrentThread());

  // Register to receive raw keyboard input.
  RAWINPUTDEVICE device = {0};
  device.dwFlags = RIDEV_INPUTSINK;
  device.usUsagePage = kGenericDesktopPage;
  device.usUsage = kKeyboardUsage;
  device.hwndTarget = hwnd;
  if (!RegisterRawInputDevices(&device, 1, sizeof(device))) {
    PLOG(ERROR) << "RegisterRawInputDevices() failed";
    return false;
  }

  registered_ = true;
  return true;
}

void KeyboardRawInputHandlerWin::Unregister() {
  DCHECK(ui_task_runner_->BelongsToCurrentThread());

  RAWINPUTDEVICE device = {0};
  device.dwFlags = RIDEV_REMOVE;
  device.usUsagePage = kGenericDesktopPage;
  device.usUsage = kKeyboardUsage;
  device.hwndTarget = nullptr;

  // The error is harmless, ignore it.
  RegisterRawInputDevices(&device, 1, sizeof(device));
  registered_ = false;
}

void KeyboardRawInputHandlerWin::OnInputEvent(const RAWINPUT* input) {
  DCHECK(ui_task_runner_->BelongsToCurrentThread());

  if (input->header.dwType == RIM_TYPEKEYBOARD &&
      input->header.hDevice != nullptr) {
    USHORT vkey = input->data.keyboard.VKey;
    UINT scancode = MapVirtualKey(vkey, MAPVK_VK_TO_VSC);
    uint32_t usb_keycode =
        ui::KeycodeConverter::NativeKeycodeToUsbKeycode(scancode);
    caller_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(on_key_event_callback_, usb_keycode));
  }
}

void KeyboardRawInputHandlerWin::OnError() {
  DCHECK(ui_task_runner_->BelongsToCurrentThread());

  if (disconnect_callback_) {
    caller_task_runner_->PostTask(FROM_HERE, std::move(disconnect_callback_));
  }
}

class LocalKeyboardInputMonitorWin : public LocalKeyboardInputMonitor {
 public:
  explicit LocalKeyboardInputMonitorWin(
      std::unique_ptr<LocalInputMonitorWin> local_input_monitor);
  ~LocalKeyboardInputMonitorWin() override;

 private:
  std::unique_ptr<LocalInputMonitorWin> local_input_monitor_;
};

LocalKeyboardInputMonitorWin::LocalKeyboardInputMonitorWin(
    std::unique_ptr<LocalInputMonitorWin> local_input_monitor)
    : local_input_monitor_(std::move(local_input_monitor)) {}

LocalKeyboardInputMonitorWin::~LocalKeyboardInputMonitorWin() = default;

}  // namespace

std::unique_ptr<LocalKeyboardInputMonitor> LocalKeyboardInputMonitor::Create(
    scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
    scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
    LocalInputMonitor::KeyPressedCallback on_key_event_callback,
    base::OnceClosure disconnect_callback) {
  auto raw_input_handler = std::make_unique<KeyboardRawInputHandlerWin>(
      caller_task_runner, ui_task_runner, std::move(on_key_event_callback),
      std::move(disconnect_callback));

  return std::make_unique<LocalKeyboardInputMonitorWin>(
      LocalInputMonitorWin::Create(caller_task_runner, ui_task_runner,
                                   std::move(raw_input_handler)));
}

}  // namespace remoting