// Copyright 2014 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/hid/hid_service_win.h" #define INITGUID #include #include #include #include #include #include "base/bind.h" #include "base/files/file.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_util.h" #include "base/strings/sys_string_conversions.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "components/device_event_log/device_event_log.h" #include "device/hid/hid_connection_win.h" #include "device/hid/hid_device_info.h" #include "net/base/io_buffer.h" // Setup API is required to enumerate HID devices. #pragma comment(lib, "setupapi.lib") #pragma comment(lib, "hid.lib") namespace device { HidServiceWin::HidServiceWin( scoped_refptr file_task_runner) : file_task_runner_(file_task_runner), device_observer_(this), weak_factory_(this) { task_runner_ = base::ThreadTaskRunnerHandle::Get(); DCHECK(task_runner_.get()); DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_HID); if (device_monitor) { device_observer_.Add(device_monitor); } file_task_runner_->PostTask( FROM_HERE, base::Bind(&HidServiceWin::EnumerateOnFileThread, weak_factory_.GetWeakPtr(), task_runner_)); } HidServiceWin::~HidServiceWin() {} void HidServiceWin::Connect(const HidDeviceId& device_id, const ConnectCallback& callback) { DCHECK(thread_checker_.CalledOnValidThread()); const auto& map_entry = devices().find(device_id); if (map_entry == devices().end()) { task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); return; } scoped_refptr device_info = map_entry->second; base::win::ScopedHandle file(OpenDevice(device_info->device_id())); if (!file.IsValid()) { HID_PLOG(EVENT) << "Failed to open device"; task_runner_->PostTask(FROM_HERE, base::Bind(callback, nullptr)); return; } task_runner_->PostTask( FROM_HERE, base::Bind(callback, make_scoped_refptr( new HidConnectionWin(device_info, std::move(file))))); } // static void HidServiceWin::EnumerateOnFileThread( base::WeakPtr service, scoped_refptr task_runner) { HDEVINFO device_info_set = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (device_info_set != INVALID_HANDLE_VALUE) { SP_DEVICE_INTERFACE_DATA device_interface_data; device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); for (int device_index = 0; SetupDiEnumDeviceInterfaces(device_info_set, NULL, &GUID_DEVINTERFACE_HID, device_index, &device_interface_data); ++device_index) { DWORD required_size = 0; // Determime the required size of detail struct. SetupDiGetDeviceInterfaceDetail(device_info_set, &device_interface_data, NULL, 0, &required_size, NULL); scoped_ptr device_interface_detail_data( static_cast(malloc(required_size))); device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); // Get the detailed data for this device. BOOL res = SetupDiGetDeviceInterfaceDetail( device_info_set, &device_interface_data, device_interface_detail_data.get(), required_size, NULL, NULL); if (!res) { continue; } std::string device_path( base::SysWideToUTF8(device_interface_detail_data->DevicePath)); DCHECK(base::IsStringASCII(device_path)); AddDeviceOnFileThread(service, task_runner, base::ToLowerASCII(device_path)); } } task_runner->PostTask( FROM_HERE, base::Bind(&HidServiceWin::FirstEnumerationComplete, service)); } // static void HidServiceWin::CollectInfoFromButtonCaps( PHIDP_PREPARSED_DATA preparsed_data, HIDP_REPORT_TYPE report_type, USHORT button_caps_length, HidCollectionInfo* collection_info) { if (button_caps_length > 0) { scoped_ptr button_caps( new HIDP_BUTTON_CAPS[button_caps_length]); if (HidP_GetButtonCaps(report_type, &button_caps[0], &button_caps_length, preparsed_data) == HIDP_STATUS_SUCCESS) { for (size_t i = 0; i < button_caps_length; i++) { int report_id = button_caps[i].ReportID; if (report_id != 0) { collection_info->report_ids.insert(report_id); } } } } } // static void HidServiceWin::CollectInfoFromValueCaps( PHIDP_PREPARSED_DATA preparsed_data, HIDP_REPORT_TYPE report_type, USHORT value_caps_length, HidCollectionInfo* collection_info) { if (value_caps_length > 0) { scoped_ptr value_caps( new HIDP_VALUE_CAPS[value_caps_length]); if (HidP_GetValueCaps( report_type, &value_caps[0], &value_caps_length, preparsed_data) == HIDP_STATUS_SUCCESS) { for (size_t i = 0; i < value_caps_length; i++) { int report_id = value_caps[i].ReportID; if (report_id != 0) { collection_info->report_ids.insert(report_id); } } } } } // static void HidServiceWin::AddDeviceOnFileThread( base::WeakPtr service, scoped_refptr task_runner, const std::string& device_path) { base::win::ScopedHandle device_handle(OpenDevice(device_path)); if (!device_handle.IsValid()) { return; } HIDD_ATTRIBUTES attrib = {0}; attrib.Size = sizeof(HIDD_ATTRIBUTES); if (!HidD_GetAttributes(device_handle.Get(), &attrib)) { HID_LOG(EVENT) << "Failed to get device attributes."; return; } PHIDP_PREPARSED_DATA preparsed_data = nullptr; if (!HidD_GetPreparsedData(device_handle.Get(), &preparsed_data) || !preparsed_data) { HID_LOG(EVENT) << "Failed to get device data."; return; } HIDP_CAPS capabilities = {0}; if (HidP_GetCaps(preparsed_data, &capabilities) != HIDP_STATUS_SUCCESS) { HID_LOG(EVENT) << "Failed to get device capabilities."; HidD_FreePreparsedData(preparsed_data); return; } // Whether or not the device includes report IDs in its reports the size // of the report ID is included in the value provided by Windows. This // appears contrary to the MSDN documentation. size_t max_input_report_size = 0; size_t max_output_report_size = 0; size_t max_feature_report_size = 0; if (capabilities.InputReportByteLength > 0) { max_input_report_size = capabilities.InputReportByteLength - 1; } if (capabilities.OutputReportByteLength > 0) { max_output_report_size = capabilities.OutputReportByteLength - 1; } if (capabilities.FeatureReportByteLength > 0) { max_feature_report_size = capabilities.FeatureReportByteLength - 1; } HidCollectionInfo collection_info; collection_info.usage = HidUsageAndPage( capabilities.Usage, static_cast(capabilities.UsagePage)); CollectInfoFromButtonCaps(preparsed_data, HidP_Input, capabilities.NumberInputButtonCaps, &collection_info); CollectInfoFromButtonCaps(preparsed_data, HidP_Output, capabilities.NumberOutputButtonCaps, &collection_info); CollectInfoFromButtonCaps(preparsed_data, HidP_Feature, capabilities.NumberFeatureButtonCaps, &collection_info); CollectInfoFromValueCaps(preparsed_data, HidP_Input, capabilities.NumberInputValueCaps, &collection_info); CollectInfoFromValueCaps(preparsed_data, HidP_Output, capabilities.NumberOutputValueCaps, &collection_info); CollectInfoFromValueCaps(preparsed_data, HidP_Feature, capabilities.NumberFeatureValueCaps, &collection_info); // 1023 characters plus NULL terminator is more than enough for a USB string // descriptor which is limited to 126 characters. wchar_t buffer[1024]; std::string product_name; if (HidD_GetProductString(device_handle.Get(), &buffer[0], sizeof(buffer))) { // NULL termination guaranteed by the API. product_name = base::SysWideToUTF8(buffer); } std::string serial_number; if (HidD_GetSerialNumberString(device_handle.Get(), &buffer[0], sizeof(buffer))) { // NULL termination guaranteed by the API. serial_number = base::SysWideToUTF8(buffer); } // This populates the HidDeviceInfo instance without a raw report descriptor. // The descriptor is unavailable on Windows because HID devices are exposed to // user-space as individual top-level collections. scoped_refptr device_info(new HidDeviceInfo( device_path, attrib.VendorID, attrib.ProductID, product_name, serial_number, kHIDBusTypeUSB, // TODO(reillyg): Detect Bluetooth. crbug.com/443335 collection_info, max_input_report_size, max_output_report_size, max_feature_report_size)); HidD_FreePreparsedData(preparsed_data); task_runner->PostTask( FROM_HERE, base::Bind(&HidServiceWin::AddDevice, service, device_info)); } void HidServiceWin::OnDeviceAdded(const GUID& class_guid, const std::string& device_path) { file_task_runner_->PostTask( FROM_HERE, base::Bind(&HidServiceWin::AddDeviceOnFileThread, weak_factory_.GetWeakPtr(), task_runner_, device_path)); } void HidServiceWin::OnDeviceRemoved(const GUID& class_guid, const std::string& device_path) { // Execute a no-op closure on the file task runner to synchronize with any // devices that are still being enumerated. file_task_runner_->PostTaskAndReply( FROM_HERE, base::Bind(&base::DoNothing), base::Bind(&HidServiceWin::RemoveDevice, weak_factory_.GetWeakPtr(), device_path)); } // static base::win::ScopedHandle HidServiceWin::OpenDevice( const std::string& device_path) { base::win::ScopedHandle file( CreateFileA(device_path.c_str(), GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL)); if (!file.IsValid() && GetLastError() == ERROR_ACCESS_DENIED) { file.Set(CreateFileA(device_path.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL)); } return file; } } // namespace device