Commit 96bd76a7 authored by Matt Reynolds's avatar Matt Reynolds Committed by Commit Bot

Block known non-gamepad HID devices in gamepad enumeration

The HID gamepad backend uses heuristics based on the button and
axis usages present in the HID report descriptor to identify
devices that may be gamepads. Misidentifying a non-gamepad as a
gamepad is problematic because Chrome only supports a maximum of
four connected gamepads. When a non-gamepad occupies one of these
slots, it prevents a real gamepad from using that slot.

This CL adds a blocklist containing device IDs for devices known
to be mis-identified by the HID gamepad heuristics. These devices
will be skipped during enumeration.

BUG=972260

Change-Id: I7279e032033b47902ea9fc046c0ab12a4370d376
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1652508Reviewed-by: default avatarOvidio de Jesús Ruiz-Henríquez <odejesush@chromium.org>
Commit-Queue: Matt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/master@{#668564}
parent bfa29865
...@@ -104,6 +104,7 @@ test("device_unittests") { ...@@ -104,6 +104,7 @@ test("device_unittests") {
"fido/u2f_sign_operation_unittest.cc", "fido/u2f_sign_operation_unittest.cc",
"fido/win/type_conversions_unittest.cc", "fido/win/type_conversions_unittest.cc",
"gamepad/abstract_haptic_gamepad_unittest.cc", "gamepad/abstract_haptic_gamepad_unittest.cc",
"gamepad/gamepad_blocklist_unittest.cc",
"gamepad/gamepad_id_list_unittest.cc", "gamepad/gamepad_id_list_unittest.cc",
"gamepad/gamepad_provider_unittest.cc", "gamepad/gamepad_provider_unittest.cc",
"gamepad/gamepad_service_unittest.cc", "gamepad/gamepad_service_unittest.cc",
......
...@@ -25,6 +25,8 @@ component("gamepad") { ...@@ -25,6 +25,8 @@ component("gamepad") {
"dualshock4_controller_win.h", "dualshock4_controller_win.h",
"game_controller_data_fetcher_mac.h", "game_controller_data_fetcher_mac.h",
"game_controller_data_fetcher_mac.mm", "game_controller_data_fetcher_mac.mm",
"gamepad_blocklist.cc",
"gamepad_blocklist.h",
"gamepad_consumer.cc", "gamepad_consumer.cc",
"gamepad_consumer.h", "gamepad_consumer.h",
"gamepad_data_fetcher.cc", "gamepad_data_fetcher.cc",
......
// Copyright 2019 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 "device/gamepad/gamepad_blocklist.h"
#include <stddef.h>
#include <algorithm>
#include <iterator>
namespace device {
namespace {
constexpr uint16_t kVendorBlue = 0xb58e;
constexpr uint16_t kVendorLenovo = 0x17ef;
constexpr uint16_t kVendorMicrosoft = 0x045e;
constexpr uint16_t kVendorOculus = 0x2833;
constexpr uint16_t kVendorSynaptics = 0x06cb;
constexpr uint16_t kVendorWacom = 0x056a;
constexpr struct VendorProductPair {
uint16_t vendor;
uint16_t product;
} kBlockedDevices[] = {
// LiteOn Lenovo Traditional USB Keyboard.
{kVendorLenovo, 0x6099},
// The Surface Pro 2017's detachable keyboard is a composite device with
// several HID sub-devices. Filter out the keyboard's device ID to avoid
// treating these sub-devices as gamepads.
{kVendorMicrosoft, 0x0922},
// The Lenovo X1 Yoga's Synaptics touchpad is recognized as a HID gamepad.
{kVendorSynaptics, 0x000f},
// The Lenovo X1 Yoga's Wacom touchscreen is recognized as a HID gamepad.
{kVendorWacom, 0x50b8},
};
// Devices from these vendors are always blocked.
constexpr uint16_t kBlockedVendors[] = {
// Some Blue Yeti microphones are recognized as gamepads.
kVendorBlue,
// Block all Oculus devices. Oculus VR controllers are handled by WebXR.
kVendorOculus,
};
} // namespace
bool GamepadIsExcluded(uint16_t vendor_id, uint16_t product_id) {
const uint16_t* vendors_begin = std::begin(kBlockedVendors);
const uint16_t* vendors_end = std::end(kBlockedVendors);
if (std::find(vendors_begin, vendors_end, vendor_id) != vendors_end)
return true;
const VendorProductPair* devices_begin = std::begin(kBlockedDevices);
const VendorProductPair* devices_end = std::end(kBlockedDevices);
return std::find_if(
devices_begin, devices_end, [=](const VendorProductPair& item) {
return vendor_id == item.vendor && product_id == item.product;
}) != devices_end;
}
} // namespace device
// Copyright 2019 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.
#ifndef DEVICE_GAMEPAD_GAMEPAD_BLOCKLIST_H_
#define DEVICE_GAMEPAD_GAMEPAD_BLOCKLIST_H_
#include <stdint.h>
#include "device/gamepad/gamepad_export.h"
namespace device {
// Returns true if a device with IDs matching |vendor_id| and |product_id|
// should not be treated as a gamepad. This is used to exclude devices that
// would otherwise be treated as a gamepad because they expose gamepad-like
// HID usages.
bool DEVICE_GAMEPAD_EXPORT GamepadIsExcluded(uint16_t vendor_id,
uint16_t product_id);
} // namespace device
#endif // DEVICE_GAMEPAD_GAMEPAD_BLOCKLIST_H_
// Copyright 2019 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 "device/gamepad/gamepad_blocklist.h"
#include "base/stl_util.h"
#include "device/gamepad/gamepad_id_list.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace device {
namespace {
// Blocked devices, taken from the gamepad blocklist.
constexpr std::pair<uint16_t, uint16_t> kBlockedDevices[] = {
{0x045e, 0x0922}, // Microsoft keyboard
{0x056a, 0x50b8}, // Wacom touchpad
{0x06cb, 0x000f}, // Synaptics touchpad
{0x17ef, 0x6099}, // Lenovo keyboard
};
constexpr size_t kBlockedDevicesLength = base::size(kBlockedDevices);
// Known devices from blocked vendors, taken from usb.ids.
// http://www.linux-usb.org/usb.ids
constexpr std::pair<uint16_t, uint16_t> kBlockedVendorDevices[] = {
{0x2833, 0x0001}, // Oculus Rift DK1 head tracker
{0x2833, 0x0021}, // Oculus Rift DK2 USB hub
{0x2833, 0x0031}, // Oculus Rift CV1 subdevice
{0x2833, 0x0101}, // Oculus latency tester
{0x2833, 0x0201}, // Oculus Rift DK2 camera
{0x2833, 0x0211}, // Oculus Rift CV1 sensor
{0x2833, 0x0330}, // Oculus Rift CV1 audio port
{0x2833, 0x1031}, // Oculus Rift CV1 subdevice
{0x2833, 0x2021}, // Oculus Rift DK2 main unit
{0x2833, 0x2031}, // Oculus Rift CV1
{0x2833, 0x3031}, // Oculus Rift CV1 subdevice
{0xb58e, 0x9e84}, // Blue Yeti Stereo Microphone
};
constexpr size_t kBlockedVendorDevicesLength =
base::size(kBlockedVendorDevices);
} // namespace
TEST(GamepadBlocklistTest, KnownGamepadsNotBlocked) {
// Known gamepads should not be excluded.
const auto& gamepads = GamepadIdList::Get().GetGamepadListForTesting();
for (const auto& item : gamepads) {
uint16_t vendor = std::get<0>(item);
uint16_t product = std::get<1>(item);
EXPECT_FALSE(GamepadIsExcluded(vendor, product));
}
}
TEST(GamepadBlocklistTest, BlockedDevices) {
for (size_t i = 0; i < kBlockedDevicesLength; ++i) {
const uint16_t vendor = kBlockedDevices[i].first;
const uint16_t product = kBlockedDevices[i].second;
// Blocked devices should be excluded.
EXPECT_TRUE(GamepadIsExcluded(vendor, product));
// Devices with product IDs close to a blocked device should not be
// excluded.
EXPECT_FALSE(GamepadIsExcluded(vendor, product - 1));
EXPECT_FALSE(GamepadIsExcluded(vendor, product + 1));
}
}
TEST(GamepadBlocklistTest, BlockedVendors) {
for (size_t i = 0; i < kBlockedVendorDevicesLength; ++i) {
const uint16_t vendor = kBlockedVendorDevices[i].first;
const uint16_t product = kBlockedVendorDevices[i].second;
// Known devices with a blocked vendor ID should be excluded.
EXPECT_TRUE(GamepadIsExcluded(vendor, product));
// Devices with nearby product IDs should also be excluded.
EXPECT_TRUE(GamepadIsExcluded(vendor, product - 1));
EXPECT_TRUE(GamepadIsExcluded(vendor, product + 1));
}
}
} // namespace device
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h" #include "base/trace_event/trace_event.h"
#include "device/gamepad/gamepad_blocklist.h"
#include "device/gamepad/gamepad_id_list.h" #include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/gamepad_uma.h" #include "device/gamepad/gamepad_uma.h"
#include "device/gamepad/nintendo_controller.h" #include "device/gamepad/nintendo_controller.h"
...@@ -174,6 +175,14 @@ void GamepadPlatformDataFetcherLinux::RefreshJoydevDevice( ...@@ -174,6 +175,14 @@ void GamepadPlatformDataFetcherLinux::RefreshJoydevDevice(
uint16_t vendor_id = device->GetVendorId(); uint16_t vendor_id = device->GetVendorId();
uint16_t product_id = device->GetProductId(); uint16_t product_id = device->GetProductId();
// Filter out devices that have gamepad-like HID usages but aren't gamepads.
if (GamepadIsExcluded(vendor_id, product_id)) {
device->CloseJoydevNode();
RemoveDevice(device);
return;
}
if (NintendoController::IsNintendoController(vendor_id, product_id)) { if (NintendoController::IsNintendoController(vendor_id, product_id)) {
// Nintendo devices are handled by the Nintendo data fetcher. // Nintendo devices are handled by the Nintendo data fetcher.
device->CloseJoydevNode(); device->CloseJoydevNode();
......
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
#include "base/mac/scoped_nsobject.h" #include "base/mac/scoped_nsobject.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "device/gamepad/gamepad_blocklist.h"
#include "device/gamepad/gamepad_id_list.h" #include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/gamepad_uma.h" #include "device/gamepad/gamepad_uma.h"
#include "device/gamepad/nintendo_controller.h" #include "device/gamepad/nintendo_controller.h"
...@@ -185,6 +186,10 @@ void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) { ...@@ -185,6 +186,10 @@ void GamepadPlatformDataFetcherMac::DeviceAdd(IOHIDDeviceRef device) {
uint16_t product_int = [product_id intValue]; uint16_t product_int = [product_id intValue];
uint16_t version_int = [version_number intValue]; uint16_t version_int = [version_number intValue];
// Filter out devices that have gamepad-like HID usages but aren't gamepads.
if (GamepadIsExcluded(vendor_int, product_int))
return;
// Nintendo devices are handled by the Nintendo data fetcher. // Nintendo devices are handled by the Nintendo data fetcher.
if (NintendoController::IsNintendoController(vendor_int, product_int)) if (NintendoController::IsNintendoController(vendor_int, product_int))
return; return;
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "raw_input_gamepad_device_win.h" #include "raw_input_gamepad_device_win.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "device/gamepad/gamepad_blocklist.h"
#include "device/gamepad/gamepad_data_fetcher.h" #include "device/gamepad/gamepad_data_fetcher.h"
namespace device { namespace device {
...@@ -31,25 +32,6 @@ const uint32_t kSearchUsageNumber = 0x0221; ...@@ -31,25 +32,6 @@ const uint32_t kSearchUsageNumber = 0x0221;
const uint32_t kHomeUsageNumber = 0x0223; const uint32_t kHomeUsageNumber = 0x0223;
const uint32_t kBackUsageNumber = 0x0224; const uint32_t kBackUsageNumber = 0x0224;
// Vendor IDs.
const uint32_t kVendorOculus = 0x2833;
const uint32_t kVendorBlue = 0xb58e;
const uint32_t kVendorMicrosoft = 0x045e;
// Product IDs.
const uint32_t kProductSurfacePro2017Keyboard = 0x0922;
struct VendorProductPair {
const uint16_t vendor;
const uint16_t product;
} kFilteredDevices[] = {
// The Surface Pro 2017's detachable keyboard is a composite device with
// several HID sub-devices. Filter out the keyboard's device ID to avoid
// treating these sub-devices as gamepads.
{kVendorMicrosoft, kProductSurfacePro2017Keyboard},
};
const size_t kFilteredDevicesLen = base::size(kFilteredDevices);
// The fetcher will collect all HID usages from the Button usage page and any // The fetcher will collect all HID usages from the Button usage page and any
// additional usages listed below. // additional usages listed below.
struct SpecialUsages { struct SpecialUsages {
...@@ -232,18 +214,10 @@ bool RawInputGamepadDeviceWin::QueryDeviceInfo() { ...@@ -232,18 +214,10 @@ bool RawInputGamepadDeviceWin::QueryDeviceInfo() {
if (!IsGamepadUsageId(usage_)) if (!IsGamepadUsageId(usage_))
return false; return false;
// This is terrible, but the Oculus Rift and some Blue Yeti microphones seem // Filter out devices that have gamepad-like HID usages but aren't gamepads.
// to think they are gamepads. Filter out any such devices. Oculus Touch is if (GamepadIsExcluded(vendor_id_, product_id_))
// handled elsewhere.
if (vendor_id_ == kVendorOculus || vendor_id_ == kVendorBlue)
return false; return false;
for (size_t i = 0; i < kFilteredDevicesLen; ++i) {
const auto& filter = kFilteredDevices[i];
if (vendor_id_ == filter.vendor && product_id_ == filter.product)
return false;
}
// Fetch the device's |name_| (RIDI_DEVICENAME). // Fetch the device's |name_| (RIDI_DEVICENAME).
if (!QueryDeviceName()) if (!QueryDeviceName())
return false; return false;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment