diff options
author | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-31 20:13:23 +0000 |
---|---|---|
committer | rockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-01-31 20:13:23 +0000 |
commit | 125724f2dd1b339cc6dc3f3bcf2b0d565765471f (patch) | |
tree | b6b1f0d2bd292f39161de8317338edb6076fd848 /device | |
parent | 64e3d73b60bbe5d3e7808c444ae1e1e392981a41 (diff) | |
download | chromium_src-125724f2dd1b339cc6dc3f3bcf2b0d565765471f.zip chromium_src-125724f2dd1b339cc6dc3f3bcf2b0d565765471f.tar.gz chromium_src-125724f2dd1b339cc6dc3f3bcf2b0d565765471f.tar.bz2 |
HID backend.
This is a continuation of https://codereview.chromium.org/143883005.
[ps#1 here is (post-revert) ps#12 there.]
BUG=290428
TBR=miket@chromium.org
Review URL: https://codereview.chromium.org/150773004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248250 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'device')
26 files changed, 2430 insertions, 0 deletions
diff --git a/device/OWNERS b/device/OWNERS index a83889f..f6716f4 100644 --- a/device/OWNERS +++ b/device/OWNERS @@ -1,3 +1,5 @@ keybuk@chromium.org gdk@chromium.org miket@chromium.org +rockot@chromium.org +rpaquay@chromium.org diff --git a/device/device_tests.gyp b/device/device_tests.gyp index e0564b5..268b270 100644 --- a/device/device_tests.gyp +++ b/device/device_tests.gyp @@ -19,6 +19,7 @@ 'bluetooth/bluetooth.gyp:device_bluetooth_mocks', 'nfc/nfc.gyp:device_nfc', 'usb/usb.gyp:device_usb', + 'hid/hid.gyp:device_hid', ], 'sources': [ 'bluetooth/bluetooth_adapter_mac_unittest.mm', @@ -33,6 +34,8 @@ 'nfc/nfc_chromeos_unittest.cc', 'nfc/nfc_ndef_record_unittest.cc', 'usb/usb_ids_unittest.cc', + 'hid/hid_connection_unittest.cc', + 'hid/hid_service_unittest.cc', ], 'conditions': [ ['chromeos==1', { diff --git a/device/hid/DEPS b/device/hid/DEPS new file mode 100644 index 0000000..6a2f02e --- /dev/null +++ b/device/hid/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net/base", +] diff --git a/device/hid/hid.gyp b/device/hid/hid.gyp new file mode 100644 index 0000000..c5c3684 --- /dev/null +++ b/device/hid/hid.gyp @@ -0,0 +1,47 @@ +# Copyright 2013 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'targets': [ + { + 'target_name': 'device_hid', + 'type': 'static_library', + 'include_dirs': [ + '../..', + ], + 'conditions': [ + ['OS=="linux"', { + 'dependencies': [ + '../../build/linux/system.gyp:udev', + ], + }], + ], + 'sources': [ + 'hid_connection.cc', + 'hid_connection.h', + 'hid_connection_linux.cc', + 'hid_connection_linux.h', + 'hid_connection_mac.cc', + 'hid_connection_mac.h', + 'hid_connection_win.cc', + 'hid_connection_win.h', + 'hid_device_info.cc', + 'hid_device_info.h', + 'hid_service.cc', + 'hid_service.h', + 'hid_service_linux.cc', + 'hid_service_linux.h', + 'hid_service_mac.cc', + 'hid_service_mac.h', + 'hid_service_win.cc', + 'hid_service_win.h', + 'hid_utils_mac.cc', + 'hid_utils_mac.h', + ], + }, + ], +} diff --git a/device/hid/hid_connection.cc b/device/hid/hid_connection.cc new file mode 100644 index 0000000..5678407 --- /dev/null +++ b/device/hid/hid_connection.cc @@ -0,0 +1,18 @@ +// Copyright (c) 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_connection.h" + +namespace device { + +HidConnection::HidConnection(HidDeviceInfo device_info) + : device_info_(device_info) {} + +HidConnection::~HidConnection() {} + +const HidDeviceInfo& HidConnection::device_info() const { + return device_info_; +} + +} // namespace device diff --git a/device/hid/hid_connection.h b/device/hid/hid_connection.h new file mode 100644 index 0000000..27792c4 --- /dev/null +++ b/device/hid/hid_connection.h @@ -0,0 +1,54 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_CONNECTION_H_ +#define DEVICE_HID_HID_CONNECTION_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/threading/thread_checker.h" +#include "device/hid/hid_device_info.h" + +namespace net { +class IOBuffer; +} + +namespace device { + +class HidConnection : public base::RefCountedThreadSafe<HidConnection> { + public: + typedef base::Callback<void(bool success, size_t size)> IOCallback; + + virtual void Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) = 0; + virtual void Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) = 0; + virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) = 0; + virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) = 0; + + const HidDeviceInfo& device_info() const; + + protected: + friend class base::RefCountedThreadSafe<HidConnection>; + friend struct HidDeviceInfo; + + HidConnection(HidDeviceInfo device_info); + virtual ~HidConnection(); + + const HidDeviceInfo device_info_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(HidConnection); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_H_ diff --git a/device/hid/hid_connection_linux.cc b/device/hid/hid_connection_linux.cc new file mode 100644 index 0000000..0722fab --- /dev/null +++ b/device/hid/hid_connection_linux.cc @@ -0,0 +1,231 @@ +// Copyright (c) 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_connection_linux.h" + +#include <errno.h> +#include <fcntl.h> +#include <libudev.h> +#include <linux/hidraw.h> +#include <string> + +#include "base/threading/thread_restrictions.h" +#include "base/tuple.h" +#include "device/hid/hid_service.h" +#include "device/hid/hid_service_linux.h" + + +namespace device { + +namespace { + +const char kHidrawSubsystem[] = "hidraw"; + +} // namespace + +HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info, + ScopedUdevDevicePtr udev_raw_device) + : HidConnection(device_info), + initialized_(false) { + DCHECK(thread_checker_.CalledOnValidThread()); + + udev_device* dev = udev_raw_device.get(); + std::string dev_node; + if (!FindHidrawDevNode(dev, &dev_node)) { + LOG(ERROR) << "Cannot open HID device as hidraw device."; + return; + } + + base::PlatformFileError error; + + int flags = base::PLATFORM_FILE_OPEN | + base::PLATFORM_FILE_READ | + base::PLATFORM_FILE_WRITE | + base::PLATFORM_FILE_EXCLUSIVE_READ | + base::PLATFORM_FILE_EXCLUSIVE_WRITE; + + base::PlatformFile device_file = base::CreatePlatformFile( + base::FilePath(dev_node), + flags, + NULL, + &error); + if (error || device_file <= 0) { + LOG(ERROR) << error; + if (device_file) + base::ClosePlatformFile(device_file); + return; + } + if (fcntl(device_file, F_SETFL, fcntl(device_file, F_GETFL) | O_NONBLOCK)) { + PLOG(ERROR) << "Failed to set non-blocking flag to device file."; + return; + } + device_file_ = device_file; + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + device_file_, + true, + base::MessageLoopForIO::WATCH_READ_WRITE, + &device_file_watcher_, + this)) { + LOG(ERROR) << "Cannot start watching file descriptor."; + return; + } + + initialized_ = true; +} + +HidConnectionLinux::~HidConnectionLinux() { + DCHECK(thread_checker_.CalledOnValidThread()); + Disconnect(); +} + +void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(fd, device_file_); + DCHECK(initialized_); + + uint8 buffer[1024] = {0}; + int bytes = read(device_file_, buffer, 1024); + if (bytes < 0) { + if (errno == EAGAIN) { + return; + } + Disconnect(); + return; + } + scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(bytes)); + memcpy(io_buffer->data(), buffer, bytes); + input_reports_.push(std::make_pair(io_buffer, bytes)); + + ProcessReadQueue(); +} + +void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {} + +void HidConnectionLinux::Disconnect() { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!initialized_) + return; + + initialized_ = false; + device_file_watcher_.StopWatchingFileDescriptor(); + close(device_file_); + while (!read_queue_.empty()) { + PendingRequest callback = read_queue_.front(); + read_queue_.pop(); + callback.c.Run(false, 0); + } +} + +void HidConnectionLinux::Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!initialized_) { + DCHECK(read_queue_.empty()); + // There might be unread reports. + if (!input_reports_.empty()){ + read_queue_.push(MakeTuple(buffer, size, callback)); + ProcessReadQueue(); + } + callback.Run(false, 0); + return; + } else { + read_queue_.push(MakeTuple(buffer, size, callback)); + ProcessReadQueue(); + } +} + +void HidConnectionLinux::Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!initialized_) { + callback.Run(false, 0); + return; + } else { + int bytes = write(device_file_, buffer->data(), size); + if (bytes < 0) { + Disconnect(); + callback.Run(false, 0); + } else { + callback.Run(true, bytes); + } + } +} + +void HidConnectionLinux::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!initialized_) { + callback.Run(false, 0); + return; + } + NOTIMPLEMENTED(); +} + +void HidConnectionLinux::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (!initialized_) { + callback.Run(false, 0); + return; + } + NOTIMPLEMENTED(); +} + +void HidConnectionLinux::ProcessReadQueue() { + while(read_queue_.size() && input_reports_.size()) { + PendingRequest request = read_queue_.front(); + read_queue_.pop(); + PendingReport report = input_reports_.front(); + if (report.second > request.b) { + request.c.Run(false, report.second); + } else { + memcpy(request.a->data(), report.first->data(), report.second); + input_reports_.pop(); + request.c.Run(true, report.second); + } + } +} + +bool HidConnectionLinux::FindHidrawDevNode(udev_device* parent, + std::string* result) { + udev* udev = udev_device_get_udev(parent); + if (!udev) + return false; + + ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev)); + if (!enumerate) + return false; + + if (udev_enumerate_add_match_subsystem(enumerate.get(), kHidrawSubsystem)) { + return false; + } + if (udev_enumerate_scan_devices(enumerate.get())) { + return false; + } + + const char* parent_path = udev_device_get_devpath(parent); + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); + for (udev_list_entry* i = devices; i != NULL; + i = udev_list_entry_get_next(i)) { + ScopedUdevDevicePtr hid_dev( + udev_device_new_from_syspath(udev, udev_list_entry_get_name(i))); + const char* raw_path = udev_device_get_devnode(hid_dev.get()); + if (strncmp(parent_path, + udev_device_get_devpath(hid_dev.get()), + strlen(parent_path)) == 0 && + raw_path) { + *result = raw_path; + return true; + } + } + + return false; +} + +} // namespace device diff --git a/device/hid/hid_connection_linux.h b/device/hid/hid_connection_linux.h new file mode 100644 index 0000000..37d6cb0 --- /dev/null +++ b/device/hid/hid_connection_linux.h @@ -0,0 +1,69 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_CONNECTION_LINUX_H_ +#define DEVICE_HID_HID_CONNECTION_LINUX_H_ + +#include "base/memory/ref_counted.h" +#include "base/platform_file.h" +#include "base/tuple.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service_linux.h" +#include "net/base/io_buffer.h" + +namespace device { + +class HidConnectionLinux : public HidConnection, + public base::MessagePumpLibevent::Watcher { + public: + HidConnectionLinux(HidDeviceInfo device_info, + ScopedUdevDevicePtr udev_raw_device); + + virtual void Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + + bool initialized() const { return initialized_; } + + // Implements base::MessagePumpLibevent::Watcher + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + friend class base::RefCountedThreadSafe<HidConnectionLinux>; + virtual ~HidConnectionLinux(); + + static bool FindHidrawDevNode(udev_device* parent, std::string* result); + + void ProcessReadQueue(); + void Disconnect(); + + base::PlatformFile device_file_; + base::MessagePumpLibevent::FileDescriptorWatcher device_file_watcher_; + + typedef std::pair<scoped_refptr<net::IOBuffer>, size_t> PendingReport; + typedef Tuple3<scoped_refptr<net::IOBuffer>, size_t, IOCallback> + PendingRequest; + + std::queue<PendingReport> input_reports_; + std::queue<PendingRequest> read_queue_; + + bool initialized_; + + DISALLOW_COPY_AND_ASSIGN(HidConnectionLinux); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_LINUX__ diff --git a/device/hid/hid_connection_mac.cc b/device/hid/hid_connection_mac.cc new file mode 100644 index 0000000..bce7113 --- /dev/null +++ b/device/hid/hid_connection_mac.cc @@ -0,0 +1,188 @@ +// Copyright (c) 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_connection_mac.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/mac/foundation_util.h" +#include "base/threading/thread_restrictions.h" +#include "base/tuple.h" +#include "device/hid/hid_service.h" +#include "device/hid/hid_service_mac.h" +#include "net/base/io_buffer.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> + +namespace device { + +HidConnectionMac::HidConnectionMac(HidServiceMac* service, + HidDeviceInfo device_info, + IOHIDDeviceRef device) + : HidConnection(device_info), + service_(service), + device_(device), + disconnected_(false) { + DCHECK(thread_checker_.CalledOnValidThread()); + + message_loop_ = base::MessageLoopProxy::current(); + + CFRetain(device); + inbound_buffer_.reset((uint8_t*) malloc(device_info.input_report_size + 1)); + IOHIDDeviceRegisterInputReportCallback( + device_.get(), + inbound_buffer_.get(), + device_info.input_report_size + 1, + &HidConnectionMac::InputReportCallback, + this); + IOHIDDeviceOpen(device_, kIOHIDOptionsTypeNone); +} +HidConnectionMac::~HidConnectionMac() { + DCHECK(thread_checker_.CalledOnValidThread()); + + while (read_queue_.size()) { + read_queue_.front().c.Run(false, 0); + read_queue_.pop(); + } + + IOHIDDeviceClose(device_, kIOHIDOptionsTypeNone); +} + +void HidConnectionMac::InputReportCallback(void * context, + IOReturn result, + void * sender, + IOHIDReportType type, + uint32_t reportID, + uint8_t * report, + CFIndex reportLength) { + HidConnectionMac* connection = reinterpret_cast<HidConnectionMac*>(context); + size_t length = reportLength + (reportID != 0); + scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(length)); + if (reportID) { + buffer->data()[0] = reportID; + memcpy(buffer->data() + 1, report, reportLength); + } else { + memcpy(buffer->data(), report, reportLength); + } + connection->message_loop_->PostTask( + FROM_HERE, + base::Bind(&HidConnectionMac::ProcessInputReport, + connection, + type, + buffer, + length)); +} + +void HidConnectionMac::ProcessReadQueue() { + DCHECK(thread_checker_.CalledOnValidThread()); + + while(read_queue_.size() && input_reports_.size()) { + PendingRead read = read_queue_.front(); + read_queue_.pop(); + PendingReport report = input_reports_.front(); + + if (read.b < report.second) { + read.c.Run(false, report.second); + } else { + memcpy(read.a->data(), report.first->data(), report.second); + input_reports_.pop(); + read.c.Run(true, report.second); + } + } +} + +void HidConnectionMac::ProcessInputReport(IOHIDReportType type, + scoped_refptr<net::IOBuffer> report, + CFIndex reportLength) { + DCHECK(thread_checker_.CalledOnValidThread()); + + input_reports_.push(std::make_pair(report, reportLength)); + ProcessReadQueue(); +} + +void HidConnectionMac::WriteReport(IOHIDReportType type, + scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (disconnected_ || !device_) { + callback.Run(false, 0); + return; + } + const unsigned char* data_to_send = + reinterpret_cast<const unsigned char*>(buffer->data()); + size_t length_to_send = size; + if (data_to_send[0] == 0x0) { + /* Not using numbered Reports. + Don't send the report number. */ + ++data_to_send; + --length_to_send; + } + IOReturn res = IOHIDDeviceSetReport(device_.get(), + type, + buffer->data()[0], /* Report ID*/ + data_to_send, + length_to_send); + if (res != kIOReturnSuccess) { + callback.Run(false, 0); + } else { + callback.Run(true, size); + } +} + +void HidConnectionMac::Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (disconnected_ || !device_) { + callback.Run(false, 0); + return; + } + read_queue_.push(MakeTuple(buffer, size, callback)); + ProcessReadQueue(); +} + +void HidConnectionMac::Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + WriteReport(kIOHIDReportTypeOutput, buffer, size, callback); +} + +void HidConnectionMac::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + WriteReport(kIOHIDReportTypeFeature, buffer, size, callback); +} + +void HidConnectionMac::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + if (disconnected_ || !device_ || device_info_.feature_report_size == 0) { + callback.Run(false, 0); + return; + } + + if (device_info_.feature_report_size != 0 && + device_info_.feature_report_size != size) { + callback.Run(false, 0); + return; + } + + CFIndex len = device_info_.feature_report_size; + IOReturn res = IOHIDDeviceGetReport(device_, + kIOHIDReportTypeFeature, + 0, + (uint8_t*) buffer->data(), + &len); + if (res == kIOReturnSuccess) + callback.Run(true, len); + else + callback.Run(false, 0); +} + +} // namespace device diff --git a/device/hid/hid_connection_mac.h b/device/hid/hid_connection_mac.h new file mode 100644 index 0000000..730eeae --- /dev/null +++ b/device/hid/hid_connection_mac.h @@ -0,0 +1,77 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_CONNECTION_MAC_H_ +#define DEVICE_HID_HID_CONNECTION_MAC_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/tuple.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service_mac.h" + +namespace net { +class IOBuffer; +} + +namespace device { + +class HidConnectionMac : public HidConnection { + public: + HidConnectionMac(HidServiceMac* service, + HidDeviceInfo device_info, + IOHIDDeviceRef device); + + virtual void Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + + private: + virtual ~HidConnectionMac(); + + static void InputReportCallback(void * context, + IOReturn result, + void * sender, + IOHIDReportType type, + uint32_t reportID, + uint8_t * report, + CFIndex reportLength); + void ProcessReadQueue(); + void ProcessInputReport(IOHIDReportType type, + scoped_refptr<net::IOBuffer> report, + CFIndex reportLength); + + void WriteReport(IOHIDReportType type, + scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback); + + HidServiceMac* service_; + scoped_refptr<base::MessageLoopProxy> message_loop_; + base::ScopedCFTypeRef<IOHIDDeviceRef> device_; + scoped_ptr_malloc<uint8_t> inbound_buffer_; + bool disconnected_; + + typedef std::pair<scoped_refptr<net::IOBuffer>, size_t> PendingReport; + std::queue<PendingReport> input_reports_; + typedef Tuple3<scoped_refptr<net::IOBuffer>, size_t, IOCallback> PendingRead; + std::queue<PendingRead> read_queue_; + + DISALLOW_COPY_AND_ASSIGN(HidConnectionMac); +}; + + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_MAC_H_ diff --git a/device/hid/hid_connection_unittest.cc b/device/hid/hid_connection_unittest.cc new file mode 100644 index 0000000..14a2ef9 --- /dev/null +++ b/device/hid/hid_connection_unittest.cc @@ -0,0 +1,130 @@ +// Copyright (c) 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 <string> +#include <vector> + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_service.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +namespace { + +using net::IOBuffer; + +const int kUSBLUFADemoVID = 0x03eb; +const int kUSBLUFADemoPID = 0x204f; +const uint64_t kReport = 0x0903a65d030f8ec9ULL; + +int g_read_times = 0; +void Read(scoped_refptr<HidConnection> conn); + +void OnRead(scoped_refptr<HidConnection> conn, + scoped_refptr<net::IOBuffer> buffer, + bool success, + size_t bytes) { + EXPECT_TRUE(success); + if (success) { + g_read_times++; + EXPECT_EQ(8U, bytes); + if (bytes == 8) { + uint64_t* data = reinterpret_cast<uint64_t*>(buffer->data()); + EXPECT_EQ(kReport, *data); + } else { + base::MessageLoop::current()->Quit(); + } + } else { + LOG(ERROR) << "~"; + g_read_times++; + } + + if (g_read_times < 3){ + base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(Read, conn)); + } else { + base::MessageLoop::current()->Quit(); + } +} + +void Read(scoped_refptr<HidConnection> conn) { + scoped_refptr<IOBuffer> buffer(new IOBuffer(8)); + conn->Read(buffer, 8, base::Bind(OnRead, conn, buffer)); +} + +void OnWriteNormal(bool success, + size_t bytes) { + ASSERT_TRUE(success); + base::MessageLoop::current()->Quit(); +} + +void WriteNormal(scoped_refptr<HidConnection> conn) { + scoped_refptr<IOBuffer> buffer(new IOBuffer(8)); + *(int64_t*)buffer->data() = kReport; + + conn->Write(buffer, 8, base::Bind(OnWriteNormal)); +} + +} // namespace + +class HidConnectionTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + message_loop_.reset(new base::MessageLoopForIO()); + service_.reset(HidService::CreateInstance()); + ASSERT_TRUE(service_); + + std::vector<HidDeviceInfo> devices; + service_->GetDevices(&devices); + for (std::vector<HidDeviceInfo>::iterator it = devices.begin(); + it != devices.end(); + ++it) { + if (it->vendor_id == kUSBLUFADemoVID && + it->product_id == kUSBLUFADemoPID) { + device_id_ = it->device_id; + return; + } + } + } + + virtual void TearDown() OVERRIDE { + service_.reset(NULL); + message_loop_.reset(NULL); + } + + std::string device_id_; + scoped_ptr<base::MessageLoopForIO> message_loop_; + scoped_ptr<HidService> service_; +}; + +TEST_F(HidConnectionTest, Create) { + scoped_refptr<HidConnection> connection = service_->Connect(device_id_); + ASSERT_TRUE(connection || device_id_.empty()); +} + +TEST_F(HidConnectionTest, Read) { + scoped_refptr<HidConnection> connection = service_->Connect(device_id_); + + if (!device_id_.empty()) { + ASSERT_TRUE(connection); + message_loop_->PostTask(FROM_HERE, base::Bind(Read, connection)); + message_loop_->Run(); + } +} + +TEST_F(HidConnectionTest, Write) { + scoped_refptr<HidConnection> connection = service_->Connect(device_id_); + + if (!device_id_.empty()) { + ASSERT_TRUE(connection); + message_loop_->PostTask(FROM_HERE, base::Bind(WriteNormal, connection)); + message_loop_->Run(); + } +} + +} // namespace device diff --git a/device/hid/hid_connection_win.cc b/device/hid/hid_connection_win.cc new file mode 100644 index 0000000..bbb158f --- /dev/null +++ b/device/hid/hid_connection_win.cc @@ -0,0 +1,279 @@ +// Copyright (c) 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_connection_win.h" + +#include <cstring> + +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/threading/thread_restrictions.h" +#include "device/hid/hid_service.h" +#include "device/hid/hid_service_win.h" +#include "net/base/io_buffer.h" + +#if defined(OS_WIN) + +#define INITGUID + +#include <windows.h> +#include <hidclass.h> + +extern "C" { + +#include <hidsdi.h> + +} + +#include <setupapi.h> +#include <winioctl.h> +#include "base/win/scoped_handle.h" + +#endif // defined(OS_WIN) + +namespace device { + +HidConnectionWin::PendingTransfer::PendingTransfer( + scoped_refptr<HidConnectionWin> conn, + scoped_refptr<net::IOBuffer> target, + scoped_refptr<net::IOBuffer> receiving, + bool is_input, + IOCallback callback) + : conn_(conn), + is_input_(is_input), + target_(target), + receiving_(receiving), + callback_(callback), + event_(CreateEvent(NULL, FALSE, FALSE, NULL)) { + memset(&overlapped_, 0, sizeof(OVERLAPPED)); + overlapped_.hEvent = event_.Get(); +} +HidConnectionWin::PendingTransfer::~PendingTransfer() { + base::MessageLoop::current()->RemoveDestructionObserver(this); +} + +void HidConnectionWin::PendingTransfer::TakeResultFromWindowsAPI(BOOL result) { + if (result || GetLastError() != ERROR_IO_PENDING) { + conn_->OnTransferFinished(this); + } else { + base::MessageLoop::current()->AddDestructionObserver(this); + AddRef(); + watcher_.StartWatching(event_.Get(), this); + } +} + +void HidConnectionWin::PendingTransfer::OnObjectSignaled(HANDLE event_handle) { + conn_->OnTransferFinished(this); + Release(); +} + +void HidConnectionWin::PendingTransfer::WillDestroyCurrentMessageLoop() { + watcher_.StopWatching(); + conn_->OnTransferCanceled(this); +} + +void HidConnectionWin::OnTransferFinished( + scoped_refptr<PendingTransfer> transfer) { + DWORD bytes_transfered; + transfers_.erase(transfer); + if (GetOverlappedResult(file_, + transfer->GetOverlapped(), + &bytes_transfered, + FALSE)) { + if (transfer->is_input_ && !device_info_.has_report_id) { + // Move one byte forward. + --bytes_transfered; + memcpy(transfer->target_->data(), + transfer->receiving_->data() + 1, + bytes_transfered); + } + transfer->callback_.Run(true, bytes_transfered); + } else { + transfer->callback_.Run(false, 0); + } +} + +void HidConnectionWin::OnTransferCanceled( + scoped_refptr<PendingTransfer> transfer) { + transfers_.erase(transfer); + transfer->callback_.Run(false, 0); +} + +HidConnectionWin::HidConnectionWin(HidDeviceInfo device_info) + : HidConnection(device_info), + available_(false) { + DCHECK(thread_checker_.CalledOnValidThread()); + file_.Set(CreateFileA(device_info.device_id.c_str(), + GENERIC_WRITE | GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL)); + available_ = file_.IsValid(); +} + +HidConnectionWin::~HidConnectionWin() { + DCHECK(thread_checker_.CalledOnValidThread()); + CancelIo(file_.Get()); +} + +void HidConnectionWin::Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const HidConnection::IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + size_t report_size = device_info_.input_report_size; + if (report_size == 0) { + // The device does not supoort input reports. + callback.Run(false, 0); + return; + } + + if (size + !device_info_.has_report_id < report_size) { + // Buffer too short. + callback.Run(false, 0); + return; + } + + scoped_refptr<net::IOBuffer> expanded_buffer; + if (!device_info_.has_report_id) { + ++size; + expanded_buffer = new net::IOBuffer(static_cast<int>(size)); + } + + scoped_refptr<PendingTransfer> transfer( + new PendingTransfer(this, buffer, expanded_buffer, true, callback)); + transfers_.insert(transfer); + transfer->TakeResultFromWindowsAPI(ReadFile(file_.Get(), + device_info_.has_report_id ? + buffer->data() : + expanded_buffer->data(), + static_cast<DWORD>(size), + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const HidConnection::IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + size_t report_size = device_info_.output_report_size; + if (report_size == 0) { + // The device does not supoort output reports. + callback.Run(false, 0); + return; + } + + if (size + !device_info_.has_report_id > report_size) { + // Size of report too long. + callback.Run(false, 0); + return; + } + + scoped_refptr<net::IOBuffer> expanded_buffer; + if (!device_info_.has_report_id) { + expanded_buffer = new net::IOBuffer( + static_cast<int>(device_info_.output_report_size)); + memset(expanded_buffer->data(), 0, device_info_.output_report_size); + memcpy(expanded_buffer->data() + 1, + buffer->data(), + size); + size++; + } + + scoped_refptr<PendingTransfer> transfer( + new PendingTransfer(this, buffer, expanded_buffer, false, callback)); + transfers_.insert(transfer); + transfer->TakeResultFromWindowsAPI( + WriteFile(file_.Get(), + device_info_.has_report_id ? + buffer->data() : expanded_buffer->data(), + static_cast<DWORD>(device_info_.output_report_size), + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + size_t report_size = device_info_.feature_report_size; + if (report_size == 0) { + // The device does not supoort input reports. + callback.Run(false, 0); + return; + } + + if (size + !device_info_.has_report_id < report_size) { + // Buffer too short. + callback.Run(false, 0); + return; + } + + scoped_refptr<net::IOBuffer> expanded_buffer; + if (!device_info_.has_report_id) { + ++size; + expanded_buffer = new net::IOBuffer(static_cast<int>(size)); + } + + scoped_refptr<PendingTransfer> transfer( + new PendingTransfer(this, buffer, expanded_buffer, true, callback)); + transfers_.insert(transfer); + transfer->TakeResultFromWindowsAPI( + DeviceIoControl(file_.Get(), + IOCTL_HID_GET_FEATURE, + NULL, + 0, + device_info_.has_report_id ? + buffer->data() : + expanded_buffer->data(), + static_cast<DWORD>(size), + NULL, + transfer->GetOverlapped())); +} + +void HidConnectionWin::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) { + DCHECK(thread_checker_.CalledOnValidThread()); + size_t report_size = device_info_.feature_report_size; + if (report_size == 0) { + // The device does not supoort output reports. + callback.Run(false, 0); + return; + } + + if (size + !device_info_.has_report_id > report_size) { + // Size of report too long. + callback.Run(false, 0); + return; + } + + scoped_refptr<net::IOBuffer> expanded_buffer; + if (!device_info_.has_report_id) { + expanded_buffer = new net::IOBuffer( + static_cast<int>(device_info_.feature_report_size)); + memset(expanded_buffer->data(), 0, device_info_.feature_report_size); + memcpy(expanded_buffer->data() + 1, + buffer->data(), + size); + size++; + } + + scoped_refptr<PendingTransfer> transfer( + new PendingTransfer(this, buffer, expanded_buffer, false, callback)); + transfer->TakeResultFromWindowsAPI( + DeviceIoControl(file_.Get(), + IOCTL_HID_SET_FEATURE, + device_info_.has_report_id ? + buffer->data() : + expanded_buffer->data(), + static_cast<DWORD>(device_info_.output_report_size), + NULL, + 0, + NULL, + transfer->GetOverlapped())); +} + +} // namespace device diff --git a/device/hid/hid_connection_win.h b/device/hid/hid_connection_win.h new file mode 100644 index 0000000..6cbb9e9 --- /dev/null +++ b/device/hid/hid_connection_win.h @@ -0,0 +1,96 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_CONNECTION_WIN_H_ +#define DEVICE_HID_HID_CONNECTION_WIN_H_ + +#include <set> +#include <windows.h> + +#include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_checker.h" +#include "base/win/object_watcher.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_device_info.h" +#include "net/base/io_buffer.h" + +namespace device { + +class HidConnectionWin : public HidConnection { + public: + struct PendingTransfer : public base::RefCounted<PendingTransfer>, + public base::win::ObjectWatcher::Delegate, + public base::MessageLoop::DestructionObserver { + public: + PendingTransfer(scoped_refptr<HidConnectionWin> conn, + scoped_refptr<net::IOBuffer> target, + scoped_refptr<net::IOBuffer> receiving, + bool is_input, + IOCallback callback); + + void TakeResultFromWindowsAPI(BOOL result); + + OVERLAPPED* GetOverlapped() { return &overlapped_; } + + // Implements base::win::ObjectWatcher::Delegate. + virtual void OnObjectSignaled(HANDLE object) OVERRIDE; + + // Implements base::MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + + private: + friend class base::RefCounted<PendingTransfer>; + friend class HidConnectionWin; + + virtual ~PendingTransfer(); + + scoped_refptr<HidConnectionWin> conn_; + bool is_input_; + scoped_refptr<net::IOBuffer> target_; + scoped_refptr<net::IOBuffer> receiving_; + IOCallback callback_; + OVERLAPPED overlapped_; + base::win::ScopedHandle event_; + base::win::ObjectWatcher watcher_; + + DISALLOW_COPY_AND_ASSIGN(PendingTransfer); + }; + + HidConnectionWin(HidDeviceInfo device_info); + + virtual void Read(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void Write(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + virtual void SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, + size_t size, + const IOCallback& callback) OVERRIDE; + + void OnTransferFinished(scoped_refptr<PendingTransfer> transfer); + void OnTransferCanceled(scoped_refptr<PendingTransfer> transfer); + + bool available() const { return available_; } + + private: + ~HidConnectionWin(); + + base::win::ScopedHandle file_; + std::set<scoped_refptr<PendingTransfer> > transfers_; + + DISALLOW_COPY_AND_ASSIGN(HidConnectionWin); + + bool available_; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_CONNECTION_WIN_H_ diff --git a/device/hid/hid_device_info.cc b/device/hid/hid_device_info.cc new file mode 100644 index 0000000..34ea686 --- /dev/null +++ b/device/hid/hid_device_info.cc @@ -0,0 +1,22 @@ +// Copyright (c) 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_device_info.h" + +namespace device { + +HidDeviceInfo::HidDeviceInfo() + : bus_type(kHIDBusTypeUSB), + vendor_id(0), + product_id(0), + input_report_size(0), + output_report_size(0), + feature_report_size(0), + usage_page(0), + usage(0), + has_report_id(false) {} + +HidDeviceInfo::~HidDeviceInfo() {} + +} // namespace device diff --git a/device/hid/hid_device_info.h b/device/hid/hid_device_info.h new file mode 100644 index 0000000..b665944 --- /dev/null +++ b/device/hid/hid_device_info.h @@ -0,0 +1,43 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_DEVICE_INFO_H_ +#define DEVICE_HID_HID_DEVICE_INFO_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace device { + +enum HidBusType { + kHIDBusTypeUSB = 0, + kHIDBusTypeBluetooth = 1, +}; + +struct HidDeviceInfo { + HidDeviceInfo(); + ~HidDeviceInfo(); + + std::string device_id; + + HidBusType bus_type; + uint16 vendor_id; + uint16 product_id; + + size_t input_report_size; + size_t output_report_size; + size_t feature_report_size; + + uint16 usage_page; + uint16 usage; + bool has_report_id; + + std::string product_name; + std::string serial_number; +}; + +} // namespace device + +#endif // DEVICE_HID_HID_DEVICE_INFO_H_ diff --git a/device/hid/hid_service.cc b/device/hid/hid_service.cc new file mode 100644 index 0000000..973e901a --- /dev/null +++ b/device/hid/hid_service.cc @@ -0,0 +1,106 @@ +// Copyright (c) 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.h" + +#include <vector> + +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/threading/thread_restrictions.h" +#include "build/build_config.h" +#include "device/hid/hid_device_info.h" + +#if defined(OS_LINUX) +#include "device/hid/hid_service_linux.h" +#elif defined(OS_MACOSX) +#include "device/hid/hid_service_mac.h" +#else +#include "device/hid/hid_service_win.h" +#endif + +namespace device { + +namespace { + +// The instance will be reset when message loop destroys. +base::LazyInstance<scoped_ptr<HidService> >::Leaky g_hid_service_ptr = + LAZY_INSTANCE_INITIALIZER; + +} // namespace + +HidService::HidService() : initialized_(false) { + base::ThreadRestrictions::AssertIOAllowed(); + DCHECK(thread_checker_.CalledOnValidThread()); + base::MessageLoop::current()->AddDestructionObserver(this); +} + +HidService::~HidService() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::MessageLoop::current()->RemoveDestructionObserver(this); +} + +void HidService::WillDestroyCurrentMessageLoop() { + DCHECK(thread_checker_.CalledOnValidThread()); + g_hid_service_ptr.Get().reset(NULL); +} + +void HidService::GetDevices(std::vector<HidDeviceInfo>* devices) { + DCHECK(thread_checker_.CalledOnValidThread()); + STLClearObject(devices); + for (DeviceMap::iterator it = devices_.begin(); + it != devices_.end(); + ++it) { + devices->push_back(it->second); + } +} + +// Fills in the device info struct of the given device_id. +bool HidService::GetInfo(std::string device_id, HidDeviceInfo* info) const { + DeviceMap::const_iterator it = devices_.find(device_id); + if (it == devices_.end()) + return false; + *info = it->second; + return true; +} + +void HidService::AddDevice(HidDeviceInfo info) { + if (!ContainsKey(devices_, info.device_id)) { + DCHECK(thread_checker_.CalledOnValidThread()); + devices_[info.device_id] = info; + } +} + +void HidService::RemoveDevice(std::string device_id) { + if (ContainsKey(devices_, device_id)) { + DCHECK(thread_checker_.CalledOnValidThread()); + devices_.erase(device_id); + } +} + +HidService* HidService::CreateInstance() { +#if defined(OS_LINUX) + return new HidServiceLinux(); +#elif defined(OS_MACOSX) + return new HidServiceMac(); +#elif defined(OS_WIN) + return new HidServiceWin(); +#else + return NULL; +#endif +} + +HidService* HidService::GetInstance() { + if (!g_hid_service_ptr.Get().get()){ + scoped_ptr<HidService> service(CreateInstance()); + + if (service && service->initialized()) + g_hid_service_ptr.Get().reset(service.release()); + } + return g_hid_service_ptr.Get().get(); +} + +} // namespace device diff --git a/device/hid/hid_service.h b/device/hid/hid_service.h new file mode 100644 index 0000000..2589220 --- /dev/null +++ b/device/hid/hid_service.h @@ -0,0 +1,79 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_SERVICE_H_ +#define DEVICE_HID_HID_SERVICE_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string16.h" +#include "base/threading/thread_checker.h" +#include "build/build_config.h" +#include "device/hid/hid_device_info.h" + +namespace device { + +namespace { + +class HidServiceContainer; + +} // namespace + +class HidConnection; +class HidService; + +class HidService : public base::MessageLoop::DestructionObserver { + public: + // Must be called on FILE thread. + static HidService* GetInstance(); + + // Enumerates and returns a list of device identifiers. + virtual void GetDevices(std::vector<HidDeviceInfo>* devices); + + // Fills in the device info struct of the given device_id. + // Returns true if succeed. + // Returns false if the device_id is invalid, with info untouched. + bool GetInfo(std::string device_id, HidDeviceInfo* info) const; + + virtual scoped_refptr<HidConnection> Connect( + std::string platform_device_id) = 0; + + // Implements base::MessageLoop::DestructionObserver + virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + + // Gets whether the HidService have been successfully initialized. + bool initialized() const { return initialized_; } + + protected: + friend class HidServiceContainer; + friend struct base::DefaultDeleter<HidService>; + friend class HidConnectionTest; + + HidService(); + virtual ~HidService(); + + static HidService* CreateInstance(); + + virtual void AddDevice(HidDeviceInfo info); + virtual void RemoveDevice(std::string platform_device_id); + + typedef std::map<std::string, HidDeviceInfo> DeviceMap; + DeviceMap devices_; + + bool initialized_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(HidService); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_H_ diff --git a/device/hid/hid_service_linux.cc b/device/hid/hid_service_linux.cc new file mode 100644 index 0000000..f02a550 --- /dev/null +++ b/device/hid/hid_service_linux.cc @@ -0,0 +1,211 @@ +// Copyright (c) 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 <libudev.h> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/callback.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/platform_file.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_split.h" +#include "base/threading/thread_restrictions.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_connection_linux.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service_linux.h" + +namespace device { + +namespace { + +const char kUdevName[] = "udev"; +const char kUdevActionAdd[] = "add"; +const char kUdevActionRemove[] = "remove"; +const char kHIDSubSystem[] = "hid"; + +const char kHIDID[] = "HID_ID"; +const char kHIDName[] = "HID_NAME"; +const char kHIDUnique[] = "HID_UNIQ"; + +} // namespace + +HidServiceLinux::HidServiceLinux() { + udev_.reset(udev_new()); + if (!udev_) { + LOG(ERROR) << "Failed to create udev."; + return; + } + monitor_.reset(udev_monitor_new_from_netlink(udev_.get(), kUdevName)); + if (!monitor_) { + LOG(ERROR) << "Failed to create udev monitor."; + return; + } + int ret = udev_monitor_filter_add_match_subsystem_devtype( + monitor_.get(), + kHIDSubSystem, + NULL); + if (ret != 0) { + LOG(ERROR) << "Failed to add udev monitor filter."; + return; + } + + ret = udev_monitor_enable_receiving(monitor_.get()); + if (ret != 0) { + LOG(ERROR) << "Failed to start udev monitoring."; + return; + } + + monitor_fd_ = udev_monitor_get_fd(monitor_.get()); + if (monitor_fd_ <= 0) { + LOG(ERROR) << "Failed to start udev monitoring."; + return; + } + + if (!base::MessageLoopForIO::current()->WatchFileDescriptor( + monitor_fd_, + true, + base::MessageLoopForIO::WATCH_READ, + &monitor_watcher_, + this)) + return; + + Enumerate(); +} + +HidServiceLinux::~HidServiceLinux() { + monitor_watcher_.StopWatchingFileDescriptor(); + close(monitor_fd_); +} + +void HidServiceLinux::Enumerate() { + scoped_ptr<udev_enumerate, UdevEnumerateDeleter> enumerate( + udev_enumerate_new(udev_.get())); + + if (!enumerate) { + LOG(ERROR) << "Failed to enumerate devices."; + return; + } + + if (udev_enumerate_add_match_subsystem(enumerate.get(), kHIDSubSystem)) { + LOG(ERROR) << "Failed to enumerate devices."; + return; + } + + if (udev_enumerate_scan_devices(enumerate.get()) != 0) { + LOG(ERROR) << "Failed to enumerate devices."; + return; + } + + // This list is managed by |enumerate|. + udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); + for (udev_list_entry* i = devices; i != NULL; + i = udev_list_entry_get_next(i)) { + ScopedUdevDevicePtr hid_dev( + udev_device_new_from_syspath(udev_.get(), udev_list_entry_get_name(i))); + if (hid_dev) { + PlatformDeviceAdd(hid_dev.get()); + } + } + + initialized_ = true; +} + +void HidServiceLinux::PlatformDeviceAdd(udev_device* device) { + if (!device) + return; + + const char* device_id = udev_device_get_syspath(device); + if (!device_id) + return; + + + HidDeviceInfo device_info; + device_info.device_id = device_id; + + uint32 int_property = 0; + const char* str_property = NULL; + + const char* hid_id = udev_device_get_property_value(device, kHIDID); + if (!hid_id) + return; + + std::vector<std::string> parts; + base::SplitString(hid_id, ':', &parts); + if (parts.size() != 3) { + return; + } + + if (HexStringToUInt(base::StringPiece(parts[1]), &int_property)) { + device_info.vendor_id = int_property; + } + + if (HexStringToUInt(base::StringPiece(parts[2]), &int_property)) { + device_info.product_id = int_property; + } + + str_property = udev_device_get_property_value(device, kHIDUnique); + if (str_property != NULL) + device_info.serial_number = str_property; + + str_property = udev_device_get_property_value(device, kHIDName); + if (str_property != NULL) + device_info.product_name = str_property; + + AddDevice(device_info); +} + +void HidServiceLinux::PlatformDeviceRemove(udev_device* raw_dev) { + // The returned the device is not referenced. + udev_device* hid_dev = + udev_device_get_parent_with_subsystem_devtype(raw_dev, "hid", NULL); + + if (!hid_dev) + return; + + const char* device_id = NULL; + device_id = udev_device_get_syspath(hid_dev); + if (device_id == NULL) + return; + + RemoveDevice(device_id); +} + +scoped_refptr<HidConnection> HidServiceLinux::Connect(std::string device_id) { + if (!ContainsKey(devices_, device_id)) + return NULL; + ScopedUdevDevicePtr hid_device( + udev_device_new_from_syspath(udev_.get(), device_id.c_str())); + if (hid_device) { + scoped_refptr<HidConnectionLinux> connection = + new HidConnectionLinux(devices_[device_id], hid_device.Pass()); + if (connection->initialized()) + return connection; + } + return NULL; +} + +void HidServiceLinux::OnFileCanReadWithoutBlocking(int fd) { + DCHECK_EQ(monitor_fd_, fd); + + ScopedUdevDevicePtr dev(udev_monitor_receive_device(monitor_.get())); + if (!dev) + return; + + std::string action(udev_device_get_action(dev.get())); + if (action == kUdevActionAdd) { + PlatformDeviceAdd(dev.get()); + } else if (action == kUdevActionRemove) { + PlatformDeviceRemove(dev.get()); + } +} + +void HidServiceLinux::OnFileCanWriteWithoutBlocking(int fd) {} + +} // namespace dev diff --git a/device/hid/hid_service_linux.h b/device/hid/hid_service_linux.h new file mode 100644 index 0000000..415db80 --- /dev/null +++ b/device/hid/hid_service_linux.h @@ -0,0 +1,64 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_SERVICE_LINUX_H_ +#define DEVICE_HID_HID_SERVICE_LINUX_H_ + +#include <libudev.h> + +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_pump_libevent.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service.h" + +namespace device { + +class HidConnection; + +template<typename T, void func(T*)> +struct Deleter { + void operator()(T* enumerate) const { + if (enumerate != NULL) + func(enumerate); + } +}; + +typedef Deleter<udev_enumerate, udev_enumerate_unref> UdevEnumerateDeleter; +typedef Deleter<udev_device, udev_device_unref> UdevDeviceDeleter; +typedef Deleter<udev, udev_unref> UdevDeleter; +typedef Deleter<udev_monitor, udev_monitor_unref> UdevMonitorDeleter; + +typedef scoped_ptr<udev_device, UdevDeviceDeleter> ScopedUdevDevicePtr; +typedef scoped_ptr<udev_enumerate, UdevEnumerateDeleter> ScopedUdevEnumeratePtr; + +class HidServiceLinux : public HidService, + public base::MessagePumpLibevent::Watcher { + public: + HidServiceLinux(); + + virtual scoped_refptr<HidConnection> Connect(std::string device_id) OVERRIDE; + + // Implements base::MessagePumpLibevent::Watcher + virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; + virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; + + private: + virtual ~HidServiceLinux(); + + void Enumerate(); + void PlatformDeviceAdd(udev_device* device); + void PlatformDeviceRemove(udev_device* raw_dev); + + scoped_ptr<udev, UdevDeleter> udev_; + scoped_ptr<udev_monitor, UdevMonitorDeleter> monitor_; + int monitor_fd_; + base::MessagePumpLibevent::FileDescriptorWatcher monitor_watcher_; + + DISALLOW_COPY_AND_ASSIGN(HidServiceLinux); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_LINUX_H_ diff --git a/device/hid/hid_service_mac.cc b/device/hid/hid_service_mac.cc new file mode 100644 index 0000000..12da7cc --- /dev/null +++ b/device/hid/hid_service_mac.cc @@ -0,0 +1,250 @@ +// Copyright (c) 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_mac.h" + +#include <map> +#include <set> +#include <string> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/lazy_instance.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string_number_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_connection_mac.h" +#include "device/hid/hid_utils_mac.h" +#include "net/base/io_buffer.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> + +namespace device { + +class HidServiceMac; + +HidServiceMac::HidServiceMac() : enumeration_runloop_init_(true, false) { + base::ThreadRestrictions::AssertIOAllowed(); + + hid_manager_ref_.reset(IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDOptionsTypeNone)); + if (!hid_manager_ref_ || + CFGetTypeID(hid_manager_ref_) != IOHIDManagerGetTypeID()) { + LOG(ERROR) << "Failed to initialize HidManager"; + return; + } + CFRetain(hid_manager_ref_); + + // Register for plug/unplug notifications. + IOHIDManagerSetDeviceMatching(hid_manager_ref_.get(), NULL); + IOHIDManagerRegisterDeviceMatchingCallback( + hid_manager_ref_.get(), + &HidServiceMac::AddDeviceCallback, + this); + IOHIDManagerRegisterDeviceRemovalCallback( + hid_manager_ref_.get(), + &HidServiceMac::RemoveDeviceCallback, + this); + + // Blocking operation to enumerate all the pre-existing devices. + Enumerate(); + + // Save the owner message loop. + message_loop_ = base::MessageLoopProxy::current(); + + // Start a thread to monitor HID device changes. + // The reason to create a new thread is that by default the only thread in the + // browser process having a CFRunLoop is the UI thread. We do not want to + // run potentially blocking routines on it. + enumeration_runloop_thread_.reset( + new base::Thread("HidService Device Enumeration Thread")); + + base::Thread::Options options; + options.message_loop_type = base::MessageLoop::TYPE_UI; + enumeration_runloop_thread_->StartWithOptions(options); + enumeration_runloop_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&HidServiceMac::ScheduleRunLoop, base::Unretained(this))); + + enumeration_runloop_init_.Wait(); + initialized_ = true; +} + +HidServiceMac::~HidServiceMac() { + enumeration_runloop_thread_->message_loop()->PostTask( + FROM_HERE, + base::Bind(&HidServiceMac::UnscheduleRunLoop, base::Unretained(this))); + + enumeration_runloop_thread_->Stop(); +} + +void HidServiceMac::ScheduleRunLoop() { + enumeration_runloop_ = CFRunLoopGetCurrent(); + + IOHIDManagerScheduleWithRunLoop( + hid_manager_ref_, + enumeration_runloop_, + kCFRunLoopDefaultMode); + + IOHIDManagerOpen(hid_manager_ref_, kIOHIDOptionsTypeNone); + + enumeration_runloop_init_.Signal(); +} + +void HidServiceMac::UnscheduleRunLoop() { + IOHIDManagerUnscheduleFromRunLoop( + hid_manager_ref_, + enumeration_runloop_, + kCFRunLoopDefaultMode); + + IOHIDManagerClose(hid_manager_ref_, kIOHIDOptionsTypeNone); + +} + +HidServiceMac* HidServiceMac::InstanceFromContext(void* context) { + return reinterpret_cast<HidServiceMac*>(context); +} + +void HidServiceMac::AddDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref) { + HidServiceMac* service = InstanceFromContext(context); + + // Takes ownership of ref. Will be released inside PlatformDeviceAdd. + CFRetain(ref); + service->message_loop_->PostTask( + FROM_HERE, + base::Bind(&HidServiceMac::PlatformAddDevice, + base::Unretained(service), + base::Unretained(ref))); +} + +void HidServiceMac::RemoveDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref) { + HidServiceMac* service = InstanceFromContext(context); + + // Takes ownership of ref. Will be released inside PlatformDeviceRemove. + CFRetain(ref); + service->message_loop_->PostTask( + FROM_HERE, + base::Bind(&HidServiceMac::PlatformRemoveDevice, + base::Unretained(service), + base::Unretained(ref))); +} + +IOHIDDeviceRef HidServiceMac::FindDevice(std::string id) { + base::ScopedCFTypeRef<CFSetRef> devices( + IOHIDManagerCopyDevices(hid_manager_ref_)); + CFIndex count = CFSetGetCount(devices); + scoped_ptr<IOHIDDeviceRef[]> device_refs(new IOHIDDeviceRef[count]); + CFSetGetValues(devices, (const void **)(device_refs.get())); + + for (CFIndex i = 0; i < count; i++) { + int32_t int_property = 0; + if (GetHidIntProperty(device_refs[i], CFSTR(kIOHIDLocationIDKey), + &int_property)) { + if (id == base::HexEncode(&int_property, sizeof(int_property))) { + return device_refs[i]; + } + } + } + + return NULL; +} + +void HidServiceMac::Enumerate() { + base::ScopedCFTypeRef<CFSetRef> devices( + IOHIDManagerCopyDevices(hid_manager_ref_)); + CFIndex count = CFSetGetCount(devices); + scoped_ptr<IOHIDDeviceRef[]> device_refs(new IOHIDDeviceRef[count]); + CFSetGetValues(devices, (const void **)(device_refs.get())); + + for (CFIndex i = 0; i < count; i++) { + // Takes ownership. Will be released inside PlatformDeviceAdd. + CFRetain(device_refs[i]); + PlatformAddDevice(device_refs[i]); + } +} + +void HidServiceMac::PlatformAddDevice(IOHIDDeviceRef raw_ref) { + HidDeviceInfo device; + int32_t int_property = 0; + std::string str_property; + + // Auto-release. + base::ScopedCFTypeRef<IOHIDDeviceRef> ref(raw_ref); + + // Unique identifier for HID device. + if (GetHidIntProperty(ref, CFSTR(kIOHIDLocationIDKey), &int_property)) { + device.device_id = base::HexEncode(&int_property, sizeof(int_property)); + } else { + // Not an available device. + return; + } + + if (GetHidIntProperty(ref, CFSTR(kIOHIDVendorIDKey), &int_property)) { + device.vendor_id = int_property; + } + if (GetHidIntProperty(ref, CFSTR(kIOHIDProductIDKey), &int_property)) { + device.product_id = int_property; + } + if (GetHidIntProperty(ref, CFSTR(kIOHIDPrimaryUsageKey), &int_property)) { + device.usage = int_property; + } + if (GetHidIntProperty(ref, CFSTR(kIOHIDPrimaryUsagePageKey), &int_property)) { + device.usage_page = int_property; + } + if (GetHidIntProperty(ref, CFSTR(kIOHIDMaxInputReportSizeKey), + &int_property)) { + device.input_report_size = int_property; + } + if (GetHidIntProperty(ref, CFSTR(kIOHIDMaxOutputReportSizeKey), + &int_property)) { + device.output_report_size = int_property; + } + if (GetHidIntProperty(ref, CFSTR(kIOHIDMaxFeatureReportSizeKey), + &int_property)) { + device.feature_report_size = int_property; + } + if (GetHidStringProperty(ref, CFSTR(kIOHIDProductKey), &str_property)) { + device.product_name = str_property; + } + if (GetHidStringProperty(ref, CFSTR(kIOHIDSerialNumberKey), &str_property)) { + device.serial_number = str_property; + } + HidService::AddDevice(device); +} + +void HidServiceMac::PlatformRemoveDevice(IOHIDDeviceRef raw_ref) { + std::string device_id; + int32_t int_property = 0; + // Auto-release. + base::ScopedCFTypeRef<IOHIDDeviceRef> ref(raw_ref); + if (!GetHidIntProperty(ref, CFSTR(kIOHIDLocationIDKey), &int_property)) { + return; + } + device_id = base::HexEncode(&int_property, sizeof(int_property)); + HidService::RemoveDevice(device_id); +} + +scoped_refptr<HidConnection> +HidServiceMac::Connect(std::string device_id) { + if (!ContainsKey(devices_, device_id)) + return NULL; + + IOHIDDeviceRef ref = FindDevice(device_id); + if (ref == NULL) + return NULL; + return scoped_refptr<HidConnection>( + new HidConnectionMac(this, devices_[device_id], ref)); +} + +} // namespace device diff --git a/device/hid/hid_service_mac.h b/device/hid/hid_service_mac.h new file mode 100644 index 0000000..9351d1e --- /dev/null +++ b/device/hid/hid_service_mac.h @@ -0,0 +1,81 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_SERVICE_MAC_H_ +#define DEVICE_HID_HID_SERVICE_MAC_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/mac/foundation_util.h" +#include "base/memory/ref_counted.h" +#include "base/memory/singleton.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string16.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "base/threading/thread_checker.h" +#include "build/build_config.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service.h" + +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> + +namespace device { + +class HidConnection; +class HidService; + +class HidServiceMac : public HidService { + public: + HidServiceMac(); + + virtual scoped_refptr<HidConnection> Connect(std::string device_id) OVERRIDE; + + private: + virtual ~HidServiceMac(); + + void ScheduleRunLoop(); + void UnscheduleRunLoop(); + + // Device changing callbacks. + static void AddDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref); + static void RemoveDeviceCallback(void* context, + IOReturn result, + void* sender, + IOHIDDeviceRef ref); + static HidServiceMac* InstanceFromContext(void* context); + + IOHIDDeviceRef FindDevice(std::string id); + + void Enumerate(); + + void PlatformAddDevice(IOHIDDeviceRef ref); + void PlatformRemoveDevice(IOHIDDeviceRef ref); + + // The message loop this object belongs to. + scoped_refptr<base::MessageLoopProxy> message_loop_; + + // Platform HID Manager + base::ScopedCFTypeRef<IOHIDManagerRef> hid_manager_ref_; + + // Enumeration thread. + scoped_ptr<base::Thread> enumeration_runloop_thread_; + CFRunLoopRef enumeration_runloop_; + base::WaitableEvent enumeration_runloop_init_; + + bool available_; + + DISALLOW_COPY_AND_ASSIGN(HidServiceMac); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_MAC_H_ diff --git a/device/hid/hid_service_unittest.cc b/device/hid/hid_service_unittest.cc new file mode 100644 index 0000000..1fa1c0a --- /dev/null +++ b/device/hid/hid_service_unittest.cc @@ -0,0 +1,33 @@ +// Copyright (c) 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 <string> +#include <vector> + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_service.h" +#include "net/base/io_buffer.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace device { + +TEST(HidServiceTest, Create) { + base::MessageLoopForIO message_loop; + HidService* service = HidService::GetInstance(); + ASSERT_TRUE(service); + + std::vector<HidDeviceInfo> devices; + service->GetDevices(&devices); + + for (std::vector<HidDeviceInfo>::iterator it = devices.begin(); + it != devices.end(); + ++it) { + ASSERT_TRUE(!it->device_id.empty()); + } +} + +} // namespace device diff --git a/device/hid/hid_service_win.cc b/device/hid/hid_service_win.cc new file mode 100644 index 0000000..cf9db15 --- /dev/null +++ b/device/hid/hid_service_win.cc @@ -0,0 +1,240 @@ +// Copyright (c) 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" + +#include <cstdlib> +#include <string> + +#include "base/callback_helpers.h" +#include "base/lazy_instance.h" +#include "base/memory/scoped_ptr.h" +#include "base/stl_util.h" +#include "base/strings/sys_string_conversions.h" +#include "device/hid/hid_connection.h" +#include "device/hid/hid_connection_win.h" +#include "device/hid/hid_service.h" +#include "net/base/io_buffer.h" + +#if defined(OS_WIN) + +#define INITGUID + +#include <windows.h> +#include <hidclass.h> + +extern "C" { + +#include <hidsdi.h> +#include <hidpi.h> + +} + +#include <setupapi.h> +#include <winioctl.h> +#include "base/win/scoped_handle.h" + +#endif // defined(OS_WIN) + +// Setup API is required to enumerate HID devices. +#pragma comment(lib, "setupapi.lib") +#pragma comment(lib, "hid.lib") + +namespace device { +namespace { + +const char kHIDClass[] = "HIDClass"; + +} // namespace + +HidServiceWin::HidServiceWin() { + initialized_ = Enumerate(); +} +HidServiceWin::~HidServiceWin() {} + +bool HidServiceWin::Enumerate() { + BOOL res; + HDEVINFO device_info_set; + SP_DEVINFO_DATA devinfo_data; + SP_DEVICE_INTERFACE_DATA device_interface_data; + + memset(&devinfo_data, 0, sizeof(SP_DEVINFO_DATA)); + devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA); + device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + device_info_set = SetupDiGetClassDevs( + &GUID_DEVINTERFACE_HID, + NULL, + NULL, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + + if (device_info_set == INVALID_HANDLE_VALUE) + return false; + + 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. + SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + NULL, + 0, + &required_size, + NULL); + + scoped_ptr_malloc<SP_DEVICE_INTERFACE_DETAIL_DATA_A> + device_interface_detail_data( + reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_A*>( + malloc(required_size))); + device_interface_detail_data->cbSize = + sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A); + + // Get the detailed data for this device. + res = SetupDiGetDeviceInterfaceDetailA(device_info_set, + &device_interface_data, + device_interface_detail_data.get(), + required_size, + NULL, + NULL); + if (!res) + continue; + + // Enumerate device info. Looking for Setup Class "HIDClass". + for (DWORD i = 0; + SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data); + i++) { + char class_name[256] = {0}; + res = SetupDiGetDeviceRegistryPropertyA(device_info_set, + &devinfo_data, + SPDRP_CLASS, + NULL, + (PBYTE) class_name, + sizeof(class_name) - 1, + NULL); + if (!res) + break; + if (memcmp(class_name, kHIDClass, sizeof(kHIDClass)) == 0) { + char driver_name[256] = {0}; + // Get bounded driver. + res = SetupDiGetDeviceRegistryPropertyA(device_info_set, + &devinfo_data, + SPDRP_DRIVER, + NULL, + (PBYTE) driver_name, + sizeof(driver_name) - 1, + NULL); + if (res) { + // Found the drive. + break; + } + } + } + + if (!res) + continue; + + PlatformAddDevice(device_interface_detail_data->DevicePath); + } + + return true; +} + +void HidServiceWin::PlatformAddDevice(std::string device_path) { + HidDeviceInfo device_info; + device_info.device_id = device_path; + + // Try to open the device. + base::win::ScopedHandle device_handle( + CreateFileA(device_path.c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + 0)); + if (!device_handle.IsValid()) + return; + + // Get VID/PID pair. + HIDD_ATTRIBUTES attrib = {0}; + attrib.Size = sizeof(HIDD_ATTRIBUTES); + if (!HidD_GetAttributes(device_handle.Get(), &attrib)) + return; + + device_info.vendor_id = attrib.VendorID; + device_info.product_id = attrib.ProductID; + + for (ULONG i = 32; + HidD_SetNumInputBuffers(device_handle.Get(), i); + i <<= 1); + + // Get usage and usage page (optional). + PHIDP_PREPARSED_DATA preparsed_data; + if (HidD_GetPreparsedData(device_handle.Get(), &preparsed_data) && + preparsed_data) { + HIDP_CAPS capabilities; + if (HidP_GetCaps(preparsed_data, &capabilities) == HIDP_STATUS_SUCCESS) { + device_info.usage = capabilities.Usage; + device_info.usage_page = capabilities.UsagePage; + device_info.input_report_size = capabilities.InputReportByteLength; + device_info.output_report_size = capabilities.OutputReportByteLength; + device_info.feature_report_size = capabilities.FeatureReportByteLength; + } + // Detect if the device supports report ids. + if (capabilities.NumberInputValueCaps > 0) { + scoped_ptr<HIDP_VALUE_CAPS[]> value_caps( + new HIDP_VALUE_CAPS[capabilities.NumberInputValueCaps]); + USHORT value_caps_length = capabilities.NumberInputValueCaps; + if (HidP_GetValueCaps(HidP_Input, &value_caps[0], &value_caps_length, + preparsed_data) == HIDP_STATUS_SUCCESS) { + device_info.has_report_id = (value_caps[0].ReportID != 0); + } + } + HidD_FreePreparsedData(preparsed_data); + } + + // Get the serial number + wchar_t str_property[512] = { 0 }; + if (HidD_GetSerialNumberString(device_handle.Get(), + str_property, + sizeof(str_property))) { + device_info.serial_number = base::SysWideToUTF8(str_property); + } + + if (HidD_GetProductString(device_handle.Get(), + str_property, + sizeof(str_property))) { + device_info.product_name = base::SysWideToUTF8(str_property); + } + + HidService::AddDevice(device_info); +} + +void HidServiceWin::PlatformRemoveDevice(std::string device_path) { + HidService::RemoveDevice(device_path); +} + +void HidServiceWin::GetDevices(std::vector<HidDeviceInfo>* devices) { + Enumerate(); + HidService::GetDevices(devices); +} + +scoped_refptr<HidConnection> HidServiceWin::Connect(std::string device_id) { + if (!ContainsKey(devices_, device_id)) return NULL; + scoped_refptr<HidConnectionWin> connection( + new HidConnectionWin(devices_[device_id])); + if (!connection->available()) { + LOG_GETLASTERROR(ERROR) << "Failed to open device."; + return NULL; + } + return connection; +} + +} // namespace device diff --git a/device/hid/hid_service_win.h b/device/hid/hid_service_win.h new file mode 100644 index 0000000..428420e --- /dev/null +++ b/device/hid/hid_service_win.h @@ -0,0 +1,42 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_SERVICE_WIN_H_ +#define DEVICE_HID_HID_SERVICE_WIN_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "device/hid/hid_device_info.h" +#include "device/hid/hid_service.h" + +namespace device { + +class HidConnection; +class HidService; + +class HidServiceWin : public HidService { + public: + HidServiceWin(); + + virtual void GetDevices(std::vector<HidDeviceInfo>* devices) OVERRIDE; + + virtual scoped_refptr<HidConnection> Connect(std::string device_id) OVERRIDE; + + private: + virtual ~HidServiceWin(); + + bool Enumerate(); + void PlatformAddDevice(std::string device_path); + void PlatformRemoveDevice(std::string device_path); + + DISALLOW_COPY_AND_ASSIGN(HidServiceWin); +}; + +} // namespace device + +#endif // DEVICE_HID_HID_SERVICE_WIN_H_ diff --git a/device/hid/hid_utils_mac.cc b/device/hid/hid_utils_mac.cc new file mode 100644 index 0000000..588041a --- /dev/null +++ b/device/hid/hid_utils_mac.cc @@ -0,0 +1,39 @@ +// Copyright (c) 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_utils_mac.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/strings/sys_string_conversions.h" + +#if defined(OS_MACOSX) +#include <CoreFoundation/CoreFoundation.h> +#include <IOKit/hid/IOHIDManager.h> +#endif + +namespace device { + +bool GetHidIntProperty(IOHIDDeviceRef device, + CFStringRef key, + int32_t* result) { + CFNumberRef ref = base::mac::CFCast<CFNumberRef>( + IOHIDDeviceGetProperty(device, key)); + return ref && CFNumberGetValue(ref, kCFNumberSInt32Type, result); +} + +bool GetHidStringProperty(IOHIDDeviceRef device, + CFStringRef key, + std::string* result) { + CFStringRef ref = base::mac::CFCast<CFStringRef>( + IOHIDDeviceGetProperty(device, key)); + if (!ref) { + return false; + } + *result = base::SysCFStringRefToUTF8(ref); + return true; +} + +} // namespace device diff --git a/device/hid/hid_utils_mac.h b/device/hid/hid_utils_mac.h new file mode 100644 index 0000000..f1349ae --- /dev/null +++ b/device/hid/hid_utils_mac.h @@ -0,0 +1,23 @@ +// Copyright (c) 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. + +#ifndef DEVICE_HID_HID_UTILS_MAC_H_ +#define DEVICE_HID_HID_UTILS_MAC_H_ + +#include "base/callback.h" +#include "base/memory/ref_counted.h" + +#include <IOKit/hid/IOHIDManager.h> + +namespace device { + +bool GetHidIntProperty(IOHIDDeviceRef device, CFStringRef key, int32_t* result); + +bool GetHidStringProperty(IOHIDDeviceRef device, + CFStringRef key, + std::string* result); + +} // namespace device + +#endif // DEVICE_HID_HID_UTILS_MAC_H_ |