blob: 5ba8b9f38f17058e3fd3882eedcefc7855fb73bd [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"
#include "base/stl_util.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 {
float NormalizeAxis(long value, long min, long max) {
return (2.f * (value - min) / static_cast<float>(max - min)) - 1.f;
}
unsigned long GetBitmask(unsigned short bits) {
return (1 << bits) - 1;
}
const uint32_t kGenericDesktopUsagePage = 0x01;
const uint32_t kGameControlsUsagePage = 0x05;
const uint32_t kButtonUsagePage = 0x09;
const uint32_t kConsumerUsagePage = 0x0c;
const uint32_t kAxisMinimumUsageNumber = 0x30;
const uint32_t kSystemMainMenuUsageNumber = 0x85;
const uint32_t kPowerUsageNumber = 0x30;
const uint32_t kSearchUsageNumber = 0x0221;
const uint32_t kHomeUsageNumber = 0x0223;
const uint32_t kBackUsageNumber = 0x0224;
// The fetcher will collect all HID usages from the Button usage page and any
// additional usages listed below.
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},
};
const size_t kSpecialUsagesLen = base::size(kSpecialUsages);
} // namespace
RawInputGamepadDeviceWin::RawInputGamepadDeviceWin(
HANDLE device_handle,
int source_id,
HidDllFunctionsWin* hid_functions)
: handle_(device_handle),
source_id_(source_id),
last_update_timestamp_(GamepadDataFetcher::CurrentTimeInMicroseconds()),
hid_functions_(hid_functions) {
::ZeroMemory(buttons_, sizeof(buttons_));
::ZeroMemory(axes_, sizeof(axes_));
if (hid_functions_->IsValid())
is_valid_ = QueryDeviceInfo();
if (is_valid_) {
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) {
DCHECK(hid_functions_->IsValid());
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;
hid_functions_->HidPGetUsagesEx()(
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 = hid_functions_->HidPGetUsagesEx()(
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 = 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;
}
}
}
}
}
// Query axis state.
ULONG axis_value = 0;
LONG scaled_axis_value = 0;
for (uint32_t i = 0; i < axes_length_; i++) {
RawGamepadAxis* axis = &axes_[i];
// If the min is < 0 we have to query the scaled value, otherwise we need
// the normal unscaled value.
if (axis->caps.LogicalMin < 0) {
status = hid_functions_->HidPGetScaledUsageValue()(
HidP_Input, axis->caps.UsagePage, 0, axis->caps.Range.UsageMin,
&scaled_axis_value, preparsed_data_,
reinterpret_cast<PCHAR>(input->data.hid.bRawData),
input->data.hid.dwSizeHid);
if (status == HIDP_STATUS_SUCCESS) {
axis->value = NormalizeAxis(scaled_axis_value, axis->caps.PhysicalMin,
axis->caps.PhysicalMax);
}
} else {
status = hid_functions_->HidPGetUsageValue()(
HidP_Input, axis->caps.UsagePage, 0, axis->caps.Range.UsageMin,
&axis_value, preparsed_data_,
reinterpret_cast<PCHAR>(input->data.hid.bRawData),
input->data.hid.dwSizeHid);
if (status == HIDP_STATUS_SUCCESS) {
axis->value = NormalizeAxis(axis_value & axis->bitmask,
axis->caps.LogicalMin & axis->bitmask,
axis->caps.LogicalMax & axis->bitmask);
}
}
}
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_;
for (unsigned int i = 0; i < buttons_length_; 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());
product_string_.resize(Gamepad::kIdLengthCap);
return hid_functions_->HidDGetProductString()(
hid_handle.Get(), &product_string_.front(), Gamepad::kIdLengthCap);
}
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() {
DCHECK(hid_functions_);
DCHECK(hid_functions_->IsValid());
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 = hid_functions_->HidPGetCaps()(preparsed_data_, &caps);
DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
QueryButtonCapabilities(caps.NumberInputButtonCaps);
QueryAxisCapabilities(caps.NumberInputValueCaps);
return true;
}
void RawInputGamepadDeviceWin::QueryButtonCapabilities(uint16_t button_count) {
DCHECK(hid_functions_);
DCHECK(hid_functions_->IsValid());
if (button_count > 0) {
std::unique_ptr<HIDP_BUTTON_CAPS[]> button_caps(
new HIDP_BUTTON_CAPS[button_count]);
NTSTATUS status = hid_functions_->HidPGetButtonCaps()(
HidP_Input, button_caps.get(), &button_count, preparsed_data_);
DCHECK_EQ(HIDP_STATUS_SUCCESS, status);
// Keep track of which button indices are in use.
std::vector<bool> button_indices_used(Gamepad::kButtonsLengthCap, false);
// Collect all inputs from the Button usage page.
QueryNormalButtonCapabilities(button_caps.get(), button_count,
&button_indices_used);
// Check for common gamepad buttons that are not on the Button usage page.
QuerySpecialButtonCapabilities(button_caps.get(), button_count,
&button_indices_used);
}
}
void RawInputGamepadDeviceWin::QueryNormalButtonCapabilities(
HIDP_BUTTON_CAPS button_caps[],
uint16_t button_count,
std::vector<bool>* button_indices_used) {
DCHECK(button_caps);
DCHECK(button_indices_used);
// Collect all inputs from the Button usage page and assign button indices
// based on the usage value.
for (size_t i = 0; i < button_count; ++i) {
uint16_t usage_page = button_caps[i].UsagePage;
uint16_t usage_min = button_caps[i].Range.UsageMin;
uint16_t usage_max = button_caps[i].Range.UsageMax;
if (usage_min == 0 || usage_max == 0)
continue;
size_t button_index_min = size_t{usage_min - 1};
size_t button_index_max = size_t{usage_max - 1};
if (usage_page == 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 j = button_index_min; j <= button_index_max; ++j)
(*button_indices_used)[j] = true;
}
}
}
void RawInputGamepadDeviceWin::QuerySpecialButtonCapabilities(
HIDP_BUTTON_CAPS button_caps[],
uint16_t button_count,
std::vector<bool>* button_indices_used) {
DCHECK(button_caps);
DCHECK(button_indices_used);
// 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 (size_t i = 0; i < button_count; ++i) {
uint16_t usage_page = button_caps[i].UsagePage;
uint16_t usage_min = button_caps[i].Range.UsageMin;
uint16_t usage_max = button_caps[i].Range.UsageMax;
for (size_t special_index = 0; special_index < kSpecialUsagesLen;
++special_index) {
const auto& special = kSpecialUsages[special_index];
if (usage_page == 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;
}
}
}
void RawInputGamepadDeviceWin::QueryAxisCapabilities(uint16_t axis_count) {
DCHECK(hid_functions_);
DCHECK(hid_functions_->IsValid());
std::unique_ptr<HIDP_VALUE_CAPS[]> axes_caps(new HIDP_VALUE_CAPS[axis_count]);
hid_functions_->HidPGetValueCaps()(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);
} 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);
}
}
if (next_index >= Gamepad::kAxesLengthCap)
break;
}
}
}
base::WeakPtr<AbstractHapticGamepad> RawInputGamepadDeviceWin::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace device