Commit 695b737d authored by Matt Reynolds's avatar Matt Reynolds Committed by Chromium LUCI CQ

[hid] Merge top-level collections on Windows

The HID spec allows a device to group its functionality into
separate top-level collections within the HID report
descriptor. On Windows, each top-level collection is
represented in the system registry as a separate logical
device node.

This CL identifies when a device exposes multiple top-level
collections and merges them into a single HIDDevice instance.

Bug: 1009715
Change-Id: I0524fee04b5e530dfa5ff724b57d2442187946be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2592149
Commit-Queue: Matt Reynolds <mattreynolds@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#839601}
parent 86e93517
......@@ -97,11 +97,17 @@ class NintendoDataFetcherTest : public DeviceServiceTestBase {
TEST_F(NintendoDataFetcherTest, UnsupportedDeviceIsIgnored) {
// Simulate an unsupported, non-Nintendo HID device.
HidDeviceInfo::PlatformDeviceIdMap platform_device_id_map;
platform_device_id_map.emplace_back(base::flat_set<uint8_t>{0},
kTestDeviceId);
std::vector<mojom::HidCollectionInfoPtr> collections;
auto collection = mojom::HidCollectionInfo::New();
collection->usage = mojom::HidUsageAndPage::New(0, 0);
collections.push_back(std::move(collection));
scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfo(
kTestDeviceId, kPhysicalDeviceId, 0x1234, 0xabcd, "Invalipad", "",
mojom::HidBusType::kHIDBusTypeUSB, std::move(collection), 0, 0, 0));
std::move(platform_device_id_map), kPhysicalDeviceId, 0x1234, 0xabcd,
"Invalipad", "", mojom::HidBusType::kHIDBusTypeUSB,
std::move(collections), 0, 0, 0));
// Add the device to the mock HID service. The HID service should notify the
// data fetcher.
......@@ -118,11 +124,17 @@ TEST_F(NintendoDataFetcherTest, UnsupportedDeviceIsIgnored) {
TEST_F(NintendoDataFetcherTest, AddAndRemoveSwitchPro) {
// Simulate a Switch Pro over USB.
HidDeviceInfo::PlatformDeviceIdMap platform_device_id_map;
platform_device_id_map.emplace_back(base::flat_set<uint8_t>{0},
kTestDeviceId);
std::vector<mojom::HidCollectionInfoPtr> collections;
auto collection = mojom::HidCollectionInfo::New();
collection->usage = mojom::HidUsageAndPage::New(0, 0);
collections.push_back(std::move(collection));
scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfo(
kTestDeviceId, kPhysicalDeviceId, 0x057e, 0x2009, "Switch Pro Controller",
"", mojom::HidBusType::kHIDBusTypeUSB, std::move(collection), 0, 63, 0));
std::move(platform_device_id_map), kPhysicalDeviceId, 0x057e, 0x2009,
"Switch Pro Controller", "", mojom::HidBusType::kHIDBusTypeUSB,
std::move(collections), 0, 63, 0));
// Add the device to the mock HID service. The HID service should notify the
// data fetcher.
......
......@@ -175,13 +175,18 @@ class HidConnectionImplTest : public DeviceServiceTestBase {
}
scoped_refptr<HidDeviceInfo> CreateTestDevice() {
HidDeviceInfo::PlatformDeviceIdMap platform_device_id_map;
platform_device_id_map.emplace_back(base::flat_set<uint8_t>{0},
kTestDeviceId);
std::vector<mojom::HidCollectionInfoPtr> collections;
auto hid_collection_info = mojom::HidCollectionInfo::New();
hid_collection_info->usage = mojom::HidUsageAndPage::New(0, 0);
hid_collection_info->report_ids.push_back(kTestReportId);
collections.push_back(std::move(hid_collection_info));
return base::MakeRefCounted<HidDeviceInfo>(
kTestDeviceId, "1", 0x1234, 0xabcd, "product name", "serial number",
mojom::HidBusType::kHIDBusTypeUSB, std::move(hid_collection_info),
kMaxReportSizeBytes, kMaxReportSizeBytes, 0);
std::move(platform_device_id_map), "1", 0x1234, 0xabcd, "product name",
"serial number", mojom::HidBusType::kHIDBusTypeUSB,
std::move(collections), kMaxReportSizeBytes, kMaxReportSizeBytes, 0);
}
std::vector<uint8_t> CreateTestReportBuffer(uint8_t report_id, size_t size) {
......
This diff is collapsed.
......@@ -11,6 +11,7 @@
#include <list>
#include "base/containers/flat_set.h"
#include "base/macros.h"
#include "base/win/scoped_handle.h"
#include "services/device/hid/hid_connection.h"
......@@ -21,9 +22,25 @@ class PendingHidTransfer;
class HidConnectionWin : public HidConnection {
public:
// On Windows, a single HID interface may be represented by multiple file
// handles where each file handle represents one top-level HID collection.
// Maintain a mapping of report IDs to open file handles so that the correct
// handle is used for each report supported by the device.
struct HidDeviceEntry {
HidDeviceEntry(base::flat_set<uint8_t> report_ids,
base::win::ScopedHandle file_handle);
~HidDeviceEntry();
// Reports with these IDs will be routed to |file_handle|.
base::flat_set<uint8_t> report_ids;
// An open file handle representing a HID top-level collection.
base::win::ScopedHandle file_handle;
};
static scoped_refptr<HidConnection> Create(
scoped_refptr<HidDeviceInfo> device_info,
base::win::ScopedHandle file,
std::vector<std::unique_ptr<HidDeviceEntry>> file_handles,
bool allow_protected_reports);
private:
......@@ -31,7 +48,7 @@ class HidConnectionWin : public HidConnection {
friend class PendingHidTransfer;
HidConnectionWin(scoped_refptr<HidDeviceInfo> device_info,
base::win::ScopedHandle file,
std::vector<std::unique_ptr<HidDeviceEntry>> file_handles,
bool allow_protected_reports);
~HidConnectionWin() override;
......@@ -44,22 +61,31 @@ class HidConnectionWin : public HidConnection {
void PlatformSendFeatureReport(scoped_refptr<base::RefCountedBytes> buffer,
WriteCallback callback) override;
void ReadNextInputReport();
void OnReadInputReport(scoped_refptr<base::RefCountedBytes> buffer,
// Start listening for input reports from all devices in |file_handles_|.
void SetUpInitialReads();
// Listen for the next input report from |file_handle|.
void ReadNextInputReportOnHandle(HANDLE file_handle);
void OnReadInputReport(HANDLE file_handle,
scoped_refptr<base::RefCountedBytes> buffer,
PendingHidTransfer* transfer,
bool signaled);
void OnReadFeatureComplete(scoped_refptr<base::RefCountedBytes> buffer,
void OnReadFeatureComplete(HANDLE file_handle,
scoped_refptr<base::RefCountedBytes> buffer,
ReadCallback callback,
PendingHidTransfer* transfer,
bool signaled);
void OnWriteComplete(WriteCallback callback,
void OnWriteComplete(HANDLE file_handle,
WriteCallback callback,
PendingHidTransfer* transfer,
bool signaled);
std::unique_ptr<PendingHidTransfer> UnlinkTransfer(
PendingHidTransfer* transfer);
HANDLE GetHandleForReportId(uint8_t report_id) const;
base::win::ScopedHandle file_;
std::vector<std::unique_ptr<HidDeviceEntry>> file_handles_;
std::list<std::unique_ptr<PendingHidTransfer>> transfers_;
......
......@@ -11,7 +11,22 @@
namespace device {
HidDeviceInfo::HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
HidDeviceInfo::PlatformDeviceIdEntry::PlatformDeviceIdEntry(
base::flat_set<uint8_t> report_ids,
HidPlatformDeviceId platform_device_id)
: report_ids(std::move(report_ids)),
platform_device_id(platform_device_id) {}
HidDeviceInfo::PlatformDeviceIdEntry::PlatformDeviceIdEntry(
const PlatformDeviceIdEntry& entry) = default;
HidDeviceInfo::PlatformDeviceIdEntry&
HidDeviceInfo::PlatformDeviceIdEntry::operator=(
const PlatformDeviceIdEntry& entry) = default;
HidDeviceInfo::PlatformDeviceIdEntry::~PlatformDeviceIdEntry() = default;
HidDeviceInfo::HidDeviceInfo(HidPlatformDeviceId platform_device_id,
const std::string& physical_device_id,
uint16_t vendor_id,
uint16_t product_id,
......@@ -19,8 +34,7 @@ HidDeviceInfo::HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
const std::string& serial_number,
mojom::HidBusType bus_type,
const std::vector<uint8_t> report_descriptor,
std::string device_node)
: platform_device_id_(platform_device_id) {
std::string device_node) {
std::vector<mojom::HidCollectionInfoPtr> collections;
bool has_report_id;
size_t max_input_report_size;
......@@ -39,6 +53,17 @@ HidDeviceInfo::HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
auto protected_feature_report_ids = HidBlocklist::Get().GetProtectedReportIds(
HidBlocklist::kReportTypeFeature, vendor_id, product_id, collections);
std::vector<uint8_t> report_ids;
if (has_report_id) {
for (const auto& collection : collections) {
report_ids.insert(report_ids.end(), collection->report_ids.begin(),
collection->report_ids.end());
}
} else {
report_ids.push_back(0);
}
platform_device_id_map_.emplace_back(report_ids, platform_device_id);
device_ = mojom::HidDeviceInfo::New(
base::GenerateGUID(), physical_device_id, vendor_id, product_id,
product_name, serial_number, bus_type, report_descriptor,
......@@ -48,21 +73,26 @@ HidDeviceInfo::HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
protected_feature_report_ids);
}
HidDeviceInfo::HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
const std::string& physical_device_id,
uint16_t vendor_id,
uint16_t product_id,
const std::string& product_name,
const std::string& serial_number,
mojom::HidBusType bus_type,
mojom::HidCollectionInfoPtr collection,
size_t max_input_report_size,
size_t max_output_report_size,
size_t max_feature_report_size)
: platform_device_id_(platform_device_id) {
std::vector<mojom::HidCollectionInfoPtr> collections;
bool has_report_id = !collection->report_ids.empty();
collections.push_back(std::move(collection));
HidDeviceInfo::HidDeviceInfo(
PlatformDeviceIdMap platform_device_id_map,
const std::string& physical_device_id,
uint16_t vendor_id,
uint16_t product_id,
const std::string& product_name,
const std::string& serial_number,
mojom::HidBusType bus_type,
std::vector<mojom::HidCollectionInfoPtr> collections,
size_t max_input_report_size,
size_t max_output_report_size,
size_t max_feature_report_size)
: platform_device_id_map_(std::move(platform_device_id_map)) {
bool has_report_id = false;
for (const auto& collection : collections) {
if (!collection->report_ids.empty()) {
has_report_id = true;
break;
}
}
auto protected_input_report_ids = HidBlocklist::Get().GetProtectedReportIds(
HidBlocklist::kReportTypeInput, vendor_id, product_id, collections);
......
......@@ -11,6 +11,7 @@
#include <string>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string16.h"
......@@ -29,7 +30,21 @@ typedef std::string HidPlatformDeviceId;
class HidDeviceInfo : public base::RefCountedThreadSafe<HidDeviceInfo> {
public:
HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
// PlatformDeviceIdMap defines a mapping from report IDs to the
// HidPlatformDeviceId responsible for handling those reports.
struct PlatformDeviceIdEntry {
PlatformDeviceIdEntry(base::flat_set<uint8_t> report_ids,
HidPlatformDeviceId platform_device_id);
PlatformDeviceIdEntry(const PlatformDeviceIdEntry& entry);
PlatformDeviceIdEntry& operator=(const PlatformDeviceIdEntry& entry);
~PlatformDeviceIdEntry();
base::flat_set<uint8_t> report_ids;
HidPlatformDeviceId platform_device_id;
};
using PlatformDeviceIdMap = std::vector<PlatformDeviceIdEntry>;
HidDeviceInfo(HidPlatformDeviceId platform_device_id,
const std::string& physical_device_id,
uint16_t vendor_id,
uint16_t product_id,
......@@ -39,14 +54,14 @@ class HidDeviceInfo : public base::RefCountedThreadSafe<HidDeviceInfo> {
const std::vector<uint8_t> report_descriptor,
std::string device_node = "");
HidDeviceInfo(const HidPlatformDeviceId& platform_device_id,
HidDeviceInfo(PlatformDeviceIdMap platform_device_id_map,
const std::string& physical_device_id,
uint16_t vendor_id,
uint16_t product_id,
const std::string& product_name,
const std::string& serial_number,
mojom::HidBusType bus_type,
mojom::HidCollectionInfoPtr collection,
std::vector<mojom::HidCollectionInfoPtr> collections,
size_t max_input_report_size,
size_t max_output_report_size,
size_t max_feature_report_size);
......@@ -55,8 +70,8 @@ class HidDeviceInfo : public base::RefCountedThreadSafe<HidDeviceInfo> {
// Device identification.
const std::string& device_guid() const { return device_->guid; }
const HidPlatformDeviceId& platform_device_id() const {
return platform_device_id_;
const PlatformDeviceIdMap& platform_device_id_map() const {
return platform_device_id_map_;
}
const std::string& physical_device_id() const {
return device_->physical_device_id;
......@@ -94,7 +109,7 @@ class HidDeviceInfo : public base::RefCountedThreadSafe<HidDeviceInfo> {
private:
friend class base::RefCountedThreadSafe<HidDeviceInfo>;
HidPlatformDeviceId platform_device_id_;
PlatformDeviceIdMap platform_device_id_map_;
mojom::HidDeviceInfoPtr device_;
DISALLOW_COPY_AND_ASSIGN(HidDeviceInfo);
......
......@@ -163,12 +163,17 @@ class HidManagerTest : public DeviceServiceTestBase {
scoped_refptr<HidDeviceInfo> AddTestDeviceWithTopLevelCollection() {
// Construct a HidDeviceInfo with a top-level collection. The collection has
// a usage ID from the FIDO usage page.
HidDeviceInfo::PlatformDeviceIdMap platform_device_id_map;
platform_device_id_map.emplace_back(base::flat_set<uint8_t>{0},
kTestDeviceIds[2]);
std::vector<mojom::HidCollectionInfoPtr> collections;
auto collection_info = mojom::HidCollectionInfo::New();
collection_info->usage = mojom::HidUsageAndPage::New(1, 0xf1d0);
collections.push_back(std::move(collection_info));
auto device_info = base::MakeRefCounted<HidDeviceInfo>(
kTestDeviceIds[2], "physical id 2", /*vendor_id=*/0, /*product_id=*/0,
"Hid Service Unit Test", "HidDevice-2",
mojom::HidBusType::kHIDBusTypeUSB, std::move(collection_info),
std::move(platform_device_id_map), "physical id 2", /*vendor_id=*/0,
/*product_id=*/0, "Hid Service Unit Test", "HidDevice-2",
mojom::HidBusType::kHIDBusTypeUSB, std::move(collections),
/*max_input_report_size=*/64, /*max_output_report_size=*/64,
/*max_feature_report_size=*/64);
mock_hid_service_->AddDevice(device_info);
......
......@@ -4,6 +4,8 @@
#include "services/device/hid/hid_service.h"
#include <sstream>
#include "base/at_exit.h"
#include "base/bind.h"
#include "base/containers/contains.h"
......@@ -24,6 +26,26 @@
namespace device {
namespace {
// Formats the platform device IDs in |platform_device_id_map| into a
// comma-separated list for logging. The report IDs are not logged.
std::string PlatformDeviceIdsToString(
const HidDeviceInfo::PlatformDeviceIdMap& platform_device_id_map) {
std::ostringstream buf("'");
bool first = true;
for (const auto& entry : platform_device_id_map) {
if (!first)
buf << "', '";
first = false;
buf << entry.platform_device_id;
}
buf << "'";
return buf.str();
}
} // namespace
void HidService::Observer::OnDeviceAdded(mojom::HidDeviceInfoPtr device_info) {}
void HidService::Observer::OnDeviceRemoved(
......@@ -74,9 +96,12 @@ HidService::~HidService() {
void HidService::AddDevice(scoped_refptr<HidDeviceInfo> device_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string device_guid =
FindDeviceIdByPlatformDeviceId(device_info->platform_device_id());
if (device_guid.empty()) {
base::Optional<std::string> found_guid = base::nullopt;
for (const auto& entry : device_info->platform_device_id_map()) {
if ((found_guid = FindDeviceGuidInDeviceMap(entry.platform_device_id)))
break;
}
if (!found_guid) {
devices_[device_info->device_guid()] = device_info;
HID_LOG(USER) << "HID device "
......@@ -84,8 +109,10 @@ void HidService::AddDevice(scoped_refptr<HidDeviceInfo> device_info) {
<< ": vendorId=" << device_info->vendor_id()
<< ", productId=" << device_info->product_id() << ", name='"
<< device_info->product_name() << "', serial='"
<< device_info->serial_number() << "', deviceId='"
<< device_info->platform_device_id() << "'";
<< device_info->serial_number() << "', deviceIds=["
<< PlatformDeviceIdsToString(
device_info->platform_device_id_map())
<< "]";
if (enumeration_ready_) {
for (auto& observer : observer_list_)
......@@ -97,18 +124,18 @@ void HidService::AddDevice(scoped_refptr<HidDeviceInfo> device_info) {
void HidService::RemoveDevice(const HidPlatformDeviceId& platform_device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
std::string device_guid = FindDeviceIdByPlatformDeviceId(platform_device_id);
if (!device_guid.empty()) {
auto found_guid = FindDeviceGuidInDeviceMap(platform_device_id);
if (found_guid) {
HID_LOG(USER) << "HID device removed: deviceId='" << platform_device_id
<< "'";
DCHECK(base::Contains(devices_, device_guid));
DCHECK(base::Contains(devices_, *found_guid));
scoped_refptr<HidDeviceInfo> device_info = devices_[device_guid];
scoped_refptr<HidDeviceInfo> device_info = devices_[*found_guid];
if (enumeration_ready_) {
for (auto& observer : observer_list_)
observer.OnDeviceRemoved(device_info->device()->Clone());
}
devices_.erase(device_guid);
devices_.erase(*found_guid);
}
}
......@@ -135,14 +162,17 @@ void HidService::FirstEnumerationComplete() {
}
}
std::string HidService::FindDeviceIdByPlatformDeviceId(
base::Optional<std::string> HidService::FindDeviceGuidInDeviceMap(
const HidPlatformDeviceId& platform_device_id) {
for (const auto& map_entry : devices_) {
if (map_entry.second->platform_device_id() == platform_device_id) {
return map_entry.first;
for (const auto& device_entry : devices_) {
const auto& platform_device_map =
device_entry.second->platform_device_id_map();
for (const auto& platform_device_entry : platform_device_map) {
if (platform_device_entry.platform_device_id == platform_device_id)
return device_entry.first;
}
}
return std::string();
return base::nullopt;
}
} // namespace device
......@@ -89,7 +89,7 @@ class HidService {
private:
void RunPendingEnumerations();
std::string FindDeviceIdByPlatformDeviceId(
base::Optional<std::string> FindDeviceGuidInDeviceMap(
const HidPlatformDeviceId& platform_device_id);
DeviceMap devices_;
......
......@@ -446,7 +446,7 @@ void HidServiceLinux::FinishOpen(std::unique_ptr<ConnectParams> params) {
DCHECK(params->fd.is_valid());
if (!base::SetNonBlocking(params->fd.get())) {
HID_PLOG(ERROR) << "Failed to set the non-blocking flag on the device fd";
HID_PLOG(DEBUG) << "Failed to set the non-blocking flag on the device fd";
std::move(params->callback).Run(nullptr);
return;
}
......
......@@ -105,7 +105,7 @@ HidServiceMac::HidServiceMac() : weak_factory_(this) {
IOServiceMatching(kIOHIDDeviceKey), FirstMatchCallback, this,
devices_added_iterator_.InitializeInto());
if (result != kIOReturnSuccess) {
HID_LOG(ERROR) << "Failed to listen for device arrival: "
HID_LOG(DEBUG) << "Failed to listen for device arrival: "
<< HexErrorCode(result);
return;
}
......@@ -118,7 +118,7 @@ HidServiceMac::HidServiceMac() : weak_factory_(this) {
IOServiceMatching(kIOHIDDeviceKey), TerminatedCallback, this,
devices_removed_iterator_.InitializeInto());
if (result != kIOReturnSuccess) {
HID_LOG(ERROR) << "Failed to listen for device removal: "
HID_LOG(DEBUG) << "Failed to listen for device removal: "
<< HexErrorCode(result);
return;
}
......@@ -157,11 +157,14 @@ base::WeakPtr<HidService> HidServiceMac::GetWeakPtr() {
// static
base::ScopedCFTypeRef<IOHIDDeviceRef> HidServiceMac::OpenOnBlockingThread(
scoped_refptr<HidDeviceInfo> device_info) {
DCHECK_EQ(device_info->platform_device_id_map().size(), 1u);
const auto& platform_device_id =
device_info->platform_device_id_map().front().platform_device_id;
base::ScopedCFTypeRef<CFDictionaryRef> matching_dict(
IORegistryEntryIDMatching(device_info->platform_device_id()));
IORegistryEntryIDMatching(platform_device_id));
if (!matching_dict.get()) {
HID_LOG(EVENT) << "Failed to create matching dictionary for ID: "
<< device_info->platform_device_id();
HID_LOG(DEBUG) << "Failed to create matching dictionary for ID: "
<< platform_device_id;
return base::ScopedCFTypeRef<IOHIDDeviceRef>();
}
......@@ -170,21 +173,20 @@ base::ScopedCFTypeRef<IOHIDDeviceRef> HidServiceMac::OpenOnBlockingThread(
base::mac::ScopedIOObject<io_service_t> service(IOServiceGetMatchingService(
kIOMasterPortDefault, matching_dict.release()));
if (!service.get()) {
HID_LOG(EVENT) << "IOService not found for ID: "
<< device_info->platform_device_id();
HID_LOG(DEBUG) << "IOService not found for ID: " << platform_device_id;
return base::ScopedCFTypeRef<IOHIDDeviceRef>();
}
base::ScopedCFTypeRef<IOHIDDeviceRef> hid_device(
IOHIDDeviceCreate(kCFAllocatorDefault, service));
if (!hid_device) {
HID_LOG(EVENT) << "Unable to create IOHIDDevice object.";
HID_LOG(DEBUG) << "Unable to create IOHIDDevice object.";
return base::ScopedCFTypeRef<IOHIDDeviceRef>();
}
IOReturn result = IOHIDDeviceOpen(hid_device, kIOHIDOptionsTypeNone);
if (result != kIOReturnSuccess) {
HID_LOG(EVENT) << "Failed to open device: " << HexErrorCode(result);
HID_LOG(DEBUG) << "Failed to open device: " << HexErrorCode(result);
return base::ScopedCFTypeRef<IOHIDDeviceRef>();
}
......
This diff is collapsed.
......@@ -141,7 +141,7 @@ class HidServiceWin : public HidService, public DeviceMonitorWin::Observer {
static void AddDeviceBlocking(
base::WeakPtr<HidServiceWin> service,
scoped_refptr<base::SequencedTaskRunner> task_runner,
const std::wstring& device_path,
const std::vector<std::wstring>& device_paths,
const std::string& physical_device_id);
// DeviceMonitorWin::Observer implementation:
......
......@@ -62,15 +62,21 @@
'Incorrect bit width for usage 0x' + hex32(usage) + '.');
};
// Returns the first top-level collection in |device.collections| with a
// usage matching |usagePage| and |usage|, or undefined if no matching
// collection was found.
const getCollectionByUsage = (device, usagePage, usage) => {
return device.collections.find(c => {
return c.usagePage == usagePage && c.usage == usage;});
}
// Returns the first device in |devices| with a top-level collection
// matching |usagePage| and |usage|, or undefined if no matching device
// was found.
const getDeviceByCollectionUsage = (devices, usagePage, usage) => {
return devices.find(d => {
return d.collections.find(c => {
return c.usagePage == usagePage && c.usage == usage;
}) !== undefined;
});
return getCollectionByUsage(d, usagePage, usage);
}) !== undefined;
}
// Returns the first report in |devices| with matching |reportType| and
......@@ -103,6 +109,12 @@
};
const checkReportMapDualshock4 = devices => {
// Expect one device with one top-level collection.
assert_equals(devices.length, 1, 'device count');
assert_equals(devices[0].collections.length, 1, 'collection count');
const collection = getCollectionByUsage(devices[0], 0x0001, 0x0005);
assert_not_equals(collection, undefined, 'game pad collection');
// Input report
const input01 = getReport(devices, 'input', 0x01);
assert_not_equals(input01, undefined, 'input report 0x01');
......@@ -282,6 +294,12 @@
};
checkReportMapDualSense = devices => {
// Expect one device with one top-level collection.
assert_equals(devices.length, 1, 'device count');
assert_equals(devices[0].collections.length, 1, 'collection count');
const collection = getCollectionByUsage(devices[0], 0x0001, 0x0005);
assert_not_equals(collection, undefined, 'game pad collection');
// Input report
const input01 = getReport(devices, 'input', 0x01);
assert_not_equals(input01, undefined, 'input report 0x01');
......@@ -380,6 +398,12 @@
};
checkReportMapSwitchPro = devices => {
// Expect one device with one top-level collection.
assert_equals(devices.length, 1, 'device count');
assert_equals(devices[0].collections.length, 1, 'collection count');
const collection = getCollectionByUsage(devices[0], 0x0001, 0x0004);
assert_not_equals(collection, undefined, 'joystick collection');
// Input reports
const input30 = getReport(devices, 'input', 0x30);
assert_not_equals(input30, undefined, 'input report 0x30');
......@@ -437,12 +461,19 @@
// Speech Mike exposes five HID interfaces, two of which are blocked by
// WebHID. None of the interfaces use report IDs. Distinguish the
// interfaces by their top-level collection usage information.
assert_equals(devices.length, 3, 'device count');
const device0 = getDeviceByCollectionUsage(devices, 0xffa0, 0x0001);
assert_not_equals(device0, undefined, 'vendor device');
assert_equals(device0.collections.length, 1,
'vendor device collection count');
const device1 = getDeviceByCollectionUsage(devices, 0x000c, 0x0001);
assert_not_equals(device1, undefined, 'consumer device');
assert_equals(device1.collections.length, 1,
'consumer device collection count');
const device2 = getDeviceByCollectionUsage(devices, 0x0001, 0x0004);
assert_not_equals(device2, undefined, 'joystick device');
assert_equals(device2.collections.length, 1,
'joystick device collection count');
// These devices should be blocked by WebHID.
const device3 = getDeviceByCollectionUsage(devices, 0x0001, 0x0002);
......@@ -492,6 +523,16 @@
};
checkReportMapEvolveLink = devices => {
// Expect one device with three top-level collections.
assert_equals(devices.length, 1, 'device count');
assert_equals(devices[0].collections.length, 3, 'collection count');
const collection0 = getCollectionByUsage(devices[0], 0x000b, 0x0005);
assert_not_equals(collection0, undefined, 'headset collection');
const collection1 = getCollectionByUsage(devices[0], 0xff00, 0x0005);
assert_not_equals(collection1, undefined, 'vendor collection');
const collection2 = getCollectionByUsage(devices[0], 0x000c, 0x0001);
assert_not_equals(collection2, undefined, 'consumer collection');
// Input reports
const input01 = getReport(devices, 'input', 0x01);
assert_not_equals(input01, undefined, 'input report 0x01');
......@@ -609,6 +650,12 @@
};
checkReportMapStadiaController = devices => {
// Expect one device with one top-level collection.
assert_equals(devices.length, 1, 'device count');
assert_equals(devices[0].collections.length, 1, 'collection count');
const collection = getCollectionByUsage(devices[0], 0x0001, 0x0005);
assert_not_equals(collection, undefined, 'game pad collection');
// Input report 0x03
const input03 = getReport(devices, 'input', 0x03);
assert_not_equals(input03, undefined, 'input report 0x03');
......
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