// 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: must be included before . clang-format will want to // reorder them. // clang-format off extern "C" { #include #include } // 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 float NormalizeAxis(T value, T min, T max) { return (2.0f * (value - min) / static_cast(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( gamepad_id, bus_type, std::make_unique(handle_)); } else if (HidHapticGamepad::IsHidHaptic(vendor_id_, product_id_)) { hid_haptics_ = HidHapticGamepad::Create( vendor_id_, product_id_, std::make_unique(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(input->data.hid.bRawData), input->data.hid.dwSizeHid); std::unique_ptr usages( new USAGE_AND_PAGE[buttons_length]); status = HidP_GetUsagesEx(HidP_Input, 0, usages.get(), &buttons_length, preparsed_data_, reinterpret_cast(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(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( mojom::GamepadEffectParametersPtr params) { if (dualshock4_) dualshock4_->SetVibration(std::move(params)); else if (hid_haptics_) hid_haptics_->SetVibration(std::move(params)); } 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(-1)) { PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; return false; } DCHECK_EQ(0u, result); std::unique_ptr buffer(new uint8_t[size]); result = ::GetRawInputDeviceInfo(handle_, RIDI_DEVICEINFO, buffer.get(), &size); if (result == static_cast(-1)) { PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; return false; } DCHECK_EQ(size, result); RID_DEVICE_INFO* device_info = reinterpret_cast(buffer.get()); DCHECK_EQ(device_info->dwType, static_cast(RIM_TYPEHID)); vendor_id_ = static_cast(device_info->hid.dwVendorId); product_id_ = static_cast(device_info->hid.dwProductId); version_number_ = static_cast(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(-1)) { PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; return false; } DCHECK_EQ(0u, result); std::unique_ptr buffer(new wchar_t[size]); result = ::GetRawInputDeviceInfo(handle_, RIDI_DEVICENAME, buffer.get(), &size); if (result == static_cast(-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(-1)) { PLOG(ERROR) << "GetRawInputDeviceInfo() failed"; return false; } DCHECK_EQ(0u, result); ppd_buffer_.reset(new uint8_t[size]); preparsed_data_ = reinterpret_cast(ppd_buffer_.get()); result = ::GetRawInputDeviceInfo(handle_, RIDI_PREPARSEDDATA, ppd_buffer_.get(), &size); if (result == static_cast(-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 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 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(usage_min - 1); size_t button_index_max = static_cast(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 button_caps) { // Check for common gamepad buttons that are not on the Button usage page. std::vector 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 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(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(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 RawInputGamepadDeviceWin::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } } // namespace device