blob: 7129777170e192cc4e94e8d4433f4ccb594d963e [file] [log] [blame]
// Copyright 2018 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 "raw_input_gamepad_device_win.h"
// NOTE: <hidsdi.h> must be included before <hidpi.h>. clang-format will want to
// reorder them.
// clang-format off
extern "C" {
#include <hidsdi.h>
#include <hidpi.h>
}
// clang-format on
#include "base/cxx17_backports.h"
#include "base/strings/string_util_win.h"
#include "base/strings/sys_string_conversions.h"
#include "device/gamepad/dualshock4_controller.h"
#include "device/gamepad/gamepad_blocklist.h"
#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/gamepad/hid_haptic_gamepad.h"
#include "device/gamepad/hid_writer_win.h"
namespace device {
namespace {
constexpr uint32_t kGenericDesktopUsagePage = 0x01;
constexpr uint32_t kGameControlsUsagePage = 0x05;
constexpr uint32_t kButtonUsagePage = 0x09;
constexpr uint32_t kConsumerUsagePage = 0x0c;
constexpr uint32_t kAxisMinimumUsageNumber = 0x30;
constexpr uint32_t kSystemMainMenuUsageNumber = 0x85;
constexpr uint32_t kPowerUsageNumber = 0x30;
constexpr uint32_t kSearchUsageNumber = 0x0221;
constexpr uint32_t kHomeUsageNumber = 0x0223;
constexpr uint32_t kBackUsageNumber = 0x0224;
// The fetcher will collect all HID usages from the Button usage page and any
// additional usages listed below.
constexpr struct SpecialUsages {
const uint16_t usage_page;
const uint16_t usage;
} kSpecialUsages[] = {
// Xbox One S pre-FW update reports Xbox button as SystemMainMenu over BT.
{kGenericDesktopUsagePage, kSystemMainMenuUsageNumber},
// Power is used for the Guide button on the Nvidia Shield 2015 gamepad.
{kConsumerUsagePage, kPowerUsageNumber},
// Search is used for the Guide button on the Nvidia Shield 2017 gamepad.
{kConsumerUsagePage, kSearchUsageNumber},
// Start, Back, and Guide buttons are often reported as Consumer Home or
// Back.
{kConsumerUsagePage, kHomeUsageNumber},
{kConsumerUsagePage, kBackUsageNumber},
};
constexpr size_t kSpecialUsagesLen = std::size(kSpecialUsages);
// Scales |value| from the range |min| <= x <= |max| to a Standard Gamepad axis
// value in the range -1.0 <= x <= 1.0.
template <class T>
float NormalizeAxis(T value, T min, T max) {
return (2.0f * (value - min) / static_cast<float>(max - min)) - 1.0f;
}
// Returns a 32-bit mask with the lowest |bits| bits set.
unsigned long GetBitmask(unsigned short bits) {
return (1 << bits) - 1;
}
} // namespace
RawInputGamepadDeviceWin::RawInputGamepadDeviceWin(HANDLE device_handle,
int source_id)
: handle_(device_handle),
source_id_(source_id),
last_update_timestamp_(GamepadDataFetcher::CurrentTimeInMicroseconds()),
button_indices_used_(Gamepad::kButtonsLengthCap, false) {
::ZeroMemory(buttons_, sizeof(buttons_));
::ZeroMemory(axes_, sizeof(axes_));
is_valid_ = QueryDeviceInfo();
if (!is_valid_)
return;
const std::string product_name = base::SysWideToUTF8(product_string_);
const GamepadId gamepad_id =
GamepadIdList::Get().GetGamepadId(product_name, vendor_id_, product_id_);
if (Dualshock4Controller::IsDualshock4(gamepad_id)) {
// Dualshock4 has different behavior over USB and Bluetooth, but the
// RawInput API does not indicate which transport is in use. Detect the
// transport type by inspecting the version number reported by the device.
GamepadBusType bus_type =
Dualshock4Controller::BusTypeFromVersionNumber(version_number_);
dualshock4_ = std::make_unique<Dualshock4Controller>(
gamepad_id, bus_type, std::make_unique<HidWriterWin>(handle_));
} else if (HidHapticGamepad::IsHidHaptic(vendor_id_, product_id_)) {
hid_haptics_ = HidHapticGamepad::Create(
vendor_id_, product_id_, std::make_unique<HidWriterWin>(handle_));
}
}
RawInputGamepadDeviceWin::~RawInputGamepadDeviceWin() = default;
// static
bool RawInputGamepadDeviceWin::IsGamepadUsageId(uint16_t usage) {
return usage == kGenericDesktopJoystick || usage == kGenericDesktopGamePad ||
usage == kGenericDesktopMultiAxisController;
}
void RawInputGamepadDeviceWin::DoShutdown() {
if (dualshock4_)
dualshock4_->Shutdown();
dualshock4_.reset();
if (hid_haptics_)
hid_haptics_->Shutdown();
hid_haptics_.reset();
}
void RawInputGamepadDeviceWin::UpdateGamepad(RAWINPUT* input) {
NTSTATUS status;
if (dualshock4_) {
// Handle Dualshock4 input reports that do not specify HID gamepad usages in
// the report descriptor.
uint8_t report_id = input->data.hid.bRawData[0];
auto report = base::make_span(input->data.hid.bRawData + 1,
input->data.hid.dwSizeHid);
Gamepad pad;
if (dualshock4_->ProcessInputReport(report_id, report, &pad)) {
for (size_t i = 0; i < Gamepad::kAxesLengthCap; ++i)
axes_[i].value = pad.axes[i];
for (size_t i = 0; i < Gamepad::kButtonsLengthCap; ++i)
buttons_[i] = pad.buttons[i].pressed;
last_update_timestamp_ = GamepadDataFetcher::CurrentTimeInMicroseconds();
return;
}
}
// Query button state.
if (buttons_length_ > 0) {
// Clear the button state
::ZeroMemory(buttons_, sizeof(buttons_));
ULONG buttons_length = 0;
HidP_GetUsagesEx(HidP_Input, 0, nullptr, &buttons_length, preparsed_data_,
reinterpret_cast<PCHAR>(input->data.hid.bRawData),
input->data.hid.dwSizeHid);
std::unique_ptr<USAGE_AND_PAGE[]> usages(
new USAGE_AND_PAGE[buttons_length]);
status = HidP_GetUsagesEx(HidP_Input, 0, usages.get(), &buttons_length,
preparsed_data_,
reinterpret_cast<PCHAR>(input->data.hid.bRawData),
input->data.hid.dwSizeHid);
if (status == HIDP_STATUS_SUCCESS) {
// Set each reported button to true.
for (size_t j = 0; j < buttons_length; j++) {
uint16_t usage_page = usages[j].UsagePage;
uint16_t usage = usages[j].Usage;
if (usage_page == kButtonUsagePage && usage > 0) {
size_t button_index = static_cast<size_t>(usage - 1);
if (button_index < Gamepad::kButtonsLengthCap)
buttons_[button_index] = true;
} else if (usage_page != kButtonUsagePage &&
!special_button_map_.empty()) {
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
int button_index = special_button_map_[special_index];
if (button_index < 0)
continue;
const auto& special = kSpecialUsages[special_index];
if (usage_page == special.usage_page && usage == special.usage)
buttons_[button_index] = true;
}
}
}
}
}
// Update axis state.
for (uint32_t axis_index = 0; axis_index < axes_length_; ++axis_index)
UpdateAxisValue(axis_index, *input);
last_update_timestamp_ = GamepadDataFetcher::CurrentTimeInMicroseconds();
}
void RawInputGamepadDeviceWin::ReadPadState(Gamepad* pad) const {
DCHECK(pad);
pad->timestamp = last_update_timestamp_;
pad->buttons_length = buttons_length_;
pad->axes_length = axes_length_;
pad->axes_used = axes_used_;
for (unsigned int i = 0; i < buttons_length_; i++) {
pad->buttons[i].used = button_indices_used_[i];
pad->buttons[i].pressed = buttons_[i];
pad->buttons[i].value = buttons_[i] ? 1.0 : 0.0;
}
for (unsigned int i = 0; i < axes_length_; i++)
pad->axes[i] = axes_[i].value;
}
bool RawInputGamepadDeviceWin::SupportsVibration() const {
return dualshock4_ || hid_haptics_;
}
void RawInputGamepadDeviceWin::SetVibration(double strong_magnitude,
double weak_magnitude) {
if (dualshock4_)
dualshock4_->SetVibration(strong_magnitude, weak_magnitude);
else if (hid_haptics_)
hid_haptics_->SetVibration(strong_magnitude, weak_magnitude);
}
bool RawInputGamepadDeviceWin::QueryDeviceInfo() {
// Fetch HID properties (RID_DEVICE_INFO_HID) for this device. This includes
// |vendor_id_|, |product_id_|, |version_number_|, and |usage_|.
if (!QueryHidInfo())
return false;
// Make sure this device is of a type that we want to observe.
if (!IsGamepadUsageId(usage_))
return false;
// Filter out devices that have gamepad-like HID usages but aren't gamepads.
if (GamepadIsExcluded(vendor_id_, product_id_))
return false;
// Fetch the device's |name_| (RIDI_DEVICENAME).
if (!QueryDeviceName())
return false;
// From the name we can guess at the bus type. PCI HID devices have "VEN" and
// "DEV" instead of "VID" and "PID". PCI HID devices are typically not
// gamepads and are ignored.
// Example PCI device name: \\?\HID#VEN_1234&DEV_ABCD
// TODO(crbug/881539): Potentially allow PCI HID devices to be enumerated, but
// prefer known gamepads when there is contention.
std::wstring pci_prefix = L"\\\\?\\HID#VEN_";
if (!name_.compare(0, pci_prefix.size(), pci_prefix))
return false;
// Filter out the virtual digitizer, which was observed after remote desktop.
// See https://crbug.com/961774 for details.
if (name_ == L"\\\\?\\VIRTUAL_DIGITIZER")
return false;
// We can now use the name to query the OS for a file handle that is used to
// read the product string from the device. If the OS does not return a valid
// handle this gamepad is invalid.
auto hid_handle = OpenHidHandle();
if (!hid_handle.IsValid())
return false;
// Fetch the human-friendly |product_string_|, if available.
if (!QueryProductString(hid_handle))
product_string_ = L"Unknown Gamepad";
// Fetch information about the buttons and axes on this device. This sets
// |buttons_length_| and |axes_length_| to their correct values and populates
// |axes_| with capabilities info.
if (!QueryDeviceCapabilities())
return false;
// Gamepads must have at least one button or axis.
if (buttons_length_ == 0 && axes_length_ == 0)
return false;
return true;
}
bool RawInputGamepadDeviceWin::QueryHidInfo() {
UINT size = 0;
UINT result =
::GetRawInputDeviceInfo(handle_, RIDI_DEVICEINFO, nullptr, &size);
if (result == static_cast<UINT>(-1)) {
PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(0u, result);
std::unique_ptr<uint8_t[]> buffer(new uint8_t[size]);
result =
::GetRawInputDeviceInfo(handle_, RIDI_DEVICEINFO, buffer.get(), &size);
if (result == static_cast<UINT>(-1)) {
PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);
RID_DEVICE_INFO* device_info =
reinterpret_cast<RID_DEVICE_INFO*>(buffer.get());
DCHECK_EQ(device_info->dwType, static_cast<DWORD>(RIM_TYPEHID));
vendor_id_ = static_cast<uint16_t>(device_info->hid.dwVendorId);
product_id_ = static_cast<uint16_t>(device_info->hid.dwProductId);
version_number_ = static_cast<uint16_t>(device_info->hid.dwVersionNumber);
usage_ = device_info->hid.usUsage;
return true;
}
bool RawInputGamepadDeviceWin::QueryDeviceName() {
UINT size = 0;
UINT result =
::GetRawInputDeviceInfo(handle_, RIDI_DEVICENAME, nullptr, &size);
if (result == static_cast<UINT>(-1)) {
PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(0u, result);
std::unique_ptr<wchar_t[]> buffer(new wchar_t[size]);
result =
::GetRawInputDeviceInfo(handle_, RIDI_DEVICENAME, buffer.get(), &size);
if (result == static_cast<UINT>(-1)) {
PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);
name_ = buffer.get();
return true;
}
bool RawInputGamepadDeviceWin::QueryProductString(
base::win::ScopedHandle& hid_handle) {
DCHECK(hid_handle.IsValid());
// HidD_GetProductString may return successfully even if it didn't write to
// the buffer. Ensure the buffer is zeroed before calling
// HidD_GetProductString. See https://crbug.com/1205511.
std::wstring buffer;
if (!HidD_GetProductString(hid_handle.Get(),
base::WriteInto(&buffer, Gamepad::kIdLengthCap),
Gamepad::kIdLengthCap)) {
return false;
}
// Remove trailing NUL characters.
buffer = std::wstring(base::TrimString(buffer, base::WStringPiece(L"\0", 1),
base::TRIM_TRAILING));
// The product string cannot be empty.
if (buffer.empty())
return false;
product_string_ = std::move(buffer);
return true;
}
base::win::ScopedHandle RawInputGamepadDeviceWin::OpenHidHandle() {
return base::win::ScopedHandle(::CreateFile(
name_.c_str(), GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, /*lpSecurityAttributes=*/nullptr,
OPEN_EXISTING, /*dwFlagsAndAttributes=*/0, /*hTemplateFile=*/nullptr));
}
bool RawInputGamepadDeviceWin::QueryDeviceCapabilities() {
UINT size = 0;
UINT result =
::GetRawInputDeviceInfo(handle_, RIDI_PREPARSEDDATA, nullptr, &size);
if (result == static_cast<UINT>(-1)) {
PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(0u, result);
ppd_buffer_.reset(new uint8_t[size]);
preparsed_data_ = reinterpret_cast<PHIDP_PREPARSED_DATA>(ppd_buffer_.get());
result = ::GetRawInputDeviceInfo(handle_, RIDI_PREPARSEDDATA,
ppd_buffer_.get(), &size);
if (result == static_cast<UINT>(-1)) {
PLOG(ERROR) << "GetRawInputDeviceInfo() failed";
return false;
}
DCHECK_EQ(size, result);
HIDP_CAPS caps;
NTSTATUS status = HidP_GetCaps(preparsed_data_, &caps);
DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
QueryButtonCapabilities(caps.NumberInputButtonCaps);
QueryAxisCapabilities(caps.NumberInputValueCaps);
return true;
}
void RawInputGamepadDeviceWin::QueryButtonCapabilities(uint16_t button_count) {
if (button_count > 0) {
std::vector<HIDP_BUTTON_CAPS> button_caps(button_count);
NTSTATUS status = HidP_GetButtonCaps(HidP_Input, button_caps.data(),
&button_count, preparsed_data_);
DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
// Collect all inputs from the Button usage page.
QueryNormalButtonCapabilities(button_caps);
// Check for common gamepad buttons that are not on the Button usage page.
QuerySpecialButtonCapabilities(button_caps);
}
}
void RawInputGamepadDeviceWin::QueryNormalButtonCapabilities(
base::span<const HIDP_BUTTON_CAPS> button_caps) {
// Collect all inputs from the Button usage page and assign button indices
// based on the usage value.
for (const auto& item : button_caps) {
uint16_t usage_min = item.Range.UsageMin;
uint16_t usage_max = item.Range.UsageMax;
if (usage_min == 0 || usage_max == 0)
continue;
size_t button_index_min = static_cast<size_t>(usage_min - 1);
size_t button_index_max = static_cast<size_t>(usage_max - 1);
if (item.UsagePage == kButtonUsagePage &&
button_index_min < Gamepad::kButtonsLengthCap) {
button_index_max =
std::min(Gamepad::kButtonsLengthCap - 1, button_index_max);
buttons_length_ = std::max(buttons_length_, button_index_max + 1);
for (size_t button_index = button_index_min;
button_index <= button_index_max; ++button_index) {
button_indices_used_[button_index] = true;
}
}
}
}
void RawInputGamepadDeviceWin::QuerySpecialButtonCapabilities(
base::span<const HIDP_BUTTON_CAPS> button_caps) {
// Check for common gamepad buttons that are not on the Button usage page.
std::vector<bool> has_special_usage(kSpecialUsagesLen, false);
size_t unmapped_button_count = 0;
for (const auto& item : button_caps) {
uint16_t usage_min = item.Range.UsageMin;
uint16_t usage_max = item.Range.UsageMax;
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
const auto& special = kSpecialUsages[special_index];
if (item.UsagePage == special.usage_page && usage_min <= special.usage &&
usage_max >= special.usage) {
has_special_usage[special_index] = true;
++unmapped_button_count;
}
}
}
special_button_map_.clear();
if (unmapped_button_count > 0) {
// Insert special buttons at unused button indices.
special_button_map_.resize(kSpecialUsagesLen, -1);
size_t button_index = 0;
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
if (!has_special_usage[special_index])
continue;
// Advance to the next unused button index.
while (button_index < Gamepad::kButtonsLengthCap &&
button_indices_used_[button_index]) {
++button_index;
}
if (button_index >= Gamepad::kButtonsLengthCap)
break;
special_button_map_[special_index] = button_index;
button_indices_used_[button_index] = true;
++button_index;
if (--unmapped_button_count == 0)
break;
}
buttons_length_ = std::max(buttons_length_, button_index);
}
}
void RawInputGamepadDeviceWin::QueryAxisCapabilities(uint16_t axis_count) {
std::unique_ptr<HIDP_VALUE_CAPS[]> axes_caps(new HIDP_VALUE_CAPS[axis_count]);
HidP_GetValueCaps(HidP_Input, axes_caps.get(), &axis_count, preparsed_data_);
bool mapped_all_axes = true;
for (size_t i = 0; i < axis_count; i++) {
size_t axis_index = axes_caps[i].Range.UsageMin - kAxisMinimumUsageNumber;
if (axis_index < Gamepad::kAxesLengthCap && !axes_[axis_index].active) {
axes_[axis_index].caps = axes_caps[i];
axes_[axis_index].value = 0;
axes_[axis_index].active = true;
axes_[axis_index].bitmask = GetBitmask(axes_caps[i].BitSize);
axes_length_ = std::max(axes_length_, axis_index + 1);
axes_used_ |= 1 << axis_index;
} else {
mapped_all_axes = false;
}
}
if (!mapped_all_axes) {
// For axes whose usage puts them outside the standard axesLengthCap range.
size_t next_index = 0;
for (size_t i = 0; i < axis_count; i++) {
size_t usage = axes_caps[i].Range.UsageMin - kAxisMinimumUsageNumber;
if (usage >= Gamepad::kAxesLengthCap &&
axes_caps[i].UsagePage <= kGameControlsUsagePage) {
for (; next_index < Gamepad::kAxesLengthCap; ++next_index) {
if (!axes_[next_index].active)
break;
}
if (next_index < Gamepad::kAxesLengthCap) {
axes_[next_index].caps = axes_caps[i];
axes_[next_index].value = 0;
axes_[next_index].active = true;
axes_[next_index].bitmask = GetBitmask(axes_caps[i].BitSize);
axes_length_ = std::max(axes_length_, next_index + 1);
axes_used_ |= 1 << next_index;
}
}
if (next_index >= Gamepad::kAxesLengthCap)
break;
}
}
}
void RawInputGamepadDeviceWin::UpdateAxisValue(size_t axis_index,
RAWINPUT& input) {
DCHECK_LT(axis_index, Gamepad::kAxesLengthCap);
// RawInput gamepad axes are normalized according to the information provided
// in the HID report descriptor. Each HID report item must specify a Logical
// Minimum and Logical Maximum to define the domain of allowable values for
// the item. An item may optionally specify a Units definition to indicate
// that the item represents a real-world value measured in those units. Items
// with a Units definition should also specify Physical Minimum and Physical
// Maximum, which are the Logical bounds transformed into Physical units.
//
// For gamepads, it is common for joystick and trigger axis items to not
// specify Units. However, D-pad items typically do specify Units. An 8-way
// directional pad, when implemented as a Hat Switch axis, reports a logical
// value from 0 to 7 that corresponds to a physical value from 0 degrees (N)
// clockwise to 315 degrees (NW).
//
// When a Hat Switch is in its Null State (no interaction) it reports a value
// outside the Logical range, often 8. Normalizing the out-of-bounds Null
// State value yields an invalid axis value greater than +1.0. This invalid
// axis value must be preserved so that downstream consumers can detect when
// the Hat Switch is reporting a Null State value.
//
// When an item provides Physical bounds, prefer to use
// HidP_GetScaledUsageValue to retrieve the item's value in real-world units
// and normalize using the Physical bounds. If the Physical bounds are invalid
// or HidP_GetScaledUsageValue fails, use HidP_GetUsageValue to retrieve the
// logical value and normalize using the Logical bounds.
auto& axis = axes_[axis_index];
if (axis.caps.PhysicalMin < axis.caps.PhysicalMax) {
LONG scaled_axis_value = 0;
if (HidP_GetScaledUsageValue(
HidP_Input, axis.caps.UsagePage, /*LinkCollection=*/0,
axis.caps.Range.UsageMin, &scaled_axis_value, preparsed_data_,
reinterpret_cast<PCHAR>(input.data.hid.bRawData),
input.data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
axis.value = NormalizeAxis(scaled_axis_value, axis.caps.PhysicalMin,
axis.caps.PhysicalMax);
return;
}
}
ULONG axis_value = 0;
if (HidP_GetUsageValue(HidP_Input, axis.caps.UsagePage, /*LinkCollection=*/0,
axis.caps.Range.UsageMin, &axis_value, preparsed_data_,
reinterpret_cast<PCHAR>(input.data.hid.bRawData),
input.data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
axis.value = NormalizeAxis(axis_value & axis.bitmask,
axis.caps.LogicalMin & axis.bitmask,
axis.caps.LogicalMax & axis.bitmask);
return;
}
axis.value = 0.0f;
}
base::WeakPtr<AbstractHapticGamepad> RawInputGamepadDeviceWin::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace device