chromium/device/gamepad/hid_writer_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 "device/gamepad/hid_writer_win.h"

#include <Unknwn.h>
#include <windows.h>

#include <WinDef.h>
#include <stdint.h>

#include "base/strings/string_util_win.h"

namespace device {

HidWriterWin::HidWriterWin(HANDLE device) {
  UINT size;
  UINT result =
      ::GetRawInputDeviceInfo(device, RIDI_DEVICENAME, nullptr, &size);
  if (result == 0U) {
    std::wstring name_buffer;
    result = ::GetRawInputDeviceInfo(
        device, RIDI_DEVICENAME, base::WriteInto(&name_buffer, size), &size);
    if (result == size) {
      // Open the device handle for asynchronous I/O.
      hid_handle_.Set(
          ::CreateFile(name_buffer.c_str(), GENERIC_READ | GENERIC_WRITE,
                       FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
                       OPEN_EXISTING, FILE_FLAG_OVERLAPPED, nullptr));
    }
  }
}

HidWriterWin::~HidWriterWin() = default;

size_t HidWriterWin::WriteOutputReport(base::span<const uint8_t> report) {
  DCHECK_GE(report.size_bytes(), 1U);
  if (!hid_handle_.IsValid())
    return 0;

  base::win::ScopedHandle event_handle(
      ::CreateEvent(nullptr, false, false, L""));
  OVERLAPPED overlapped = {0};
  overlapped.hEvent = event_handle.Get();

  // Set up an asynchronous write.
  DWORD bytes_written = 0;
  BOOL write_success =
      ::WriteFile(hid_handle_.Get(), report.data(), report.size_bytes(),
                  &bytes_written, &overlapped);
  if (!write_success) {
    DWORD error = ::GetLastError();
    if (error == ERROR_IO_PENDING) {
      // Wait for the write to complete. This causes WriteOutputReport to behave
      // synchronously.
      DWORD wait_object = ::WaitForSingleObject(overlapped.hEvent, 100);
      if (wait_object == WAIT_OBJECT_0) {
        ::GetOverlappedResult(hid_handle_.Get(), &overlapped, &bytes_written,
                              true);
      } else {
        // Wait failed, or the timeout was exceeded before the write completed.
        // Cancel the write request.
        if (::CancelIo(hid_handle_.Get())) {
          HANDLE handles[2];
          handles[0] = hid_handle_.Get();
          handles[1] = overlapped.hEvent;
          ::WaitForMultipleObjects(2, handles, false, INFINITE);
        }
      }
    }
  }
  return write_success ? bytes_written : 0;
}

}  // namespace device