summaryrefslogtreecommitdiffstats
path: root/device
diff options
context:
space:
mode:
authorrockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-31 20:13:23 +0000
committerrockot@chromium.org <rockot@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-01-31 20:13:23 +0000
commit125724f2dd1b339cc6dc3f3bcf2b0d565765471f (patch)
treeb6b1f0d2bd292f39161de8317338edb6076fd848 /device
parent64e3d73b60bbe5d3e7808c444ae1e1e392981a41 (diff)
downloadchromium_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')
-rw-r--r--device/OWNERS2
-rw-r--r--device/device_tests.gyp3
-rw-r--r--device/hid/DEPS3
-rw-r--r--device/hid/hid.gyp47
-rw-r--r--device/hid/hid_connection.cc18
-rw-r--r--device/hid/hid_connection.h54
-rw-r--r--device/hid/hid_connection_linux.cc231
-rw-r--r--device/hid/hid_connection_linux.h69
-rw-r--r--device/hid/hid_connection_mac.cc188
-rw-r--r--device/hid/hid_connection_mac.h77
-rw-r--r--device/hid/hid_connection_unittest.cc130
-rw-r--r--device/hid/hid_connection_win.cc279
-rw-r--r--device/hid/hid_connection_win.h96
-rw-r--r--device/hid/hid_device_info.cc22
-rw-r--r--device/hid/hid_device_info.h43
-rw-r--r--device/hid/hid_service.cc106
-rw-r--r--device/hid/hid_service.h79
-rw-r--r--device/hid/hid_service_linux.cc211
-rw-r--r--device/hid/hid_service_linux.h64
-rw-r--r--device/hid/hid_service_mac.cc250
-rw-r--r--device/hid/hid_service_mac.h81
-rw-r--r--device/hid/hid_service_unittest.cc33
-rw-r--r--device/hid/hid_service_win.cc240
-rw-r--r--device/hid/hid_service_win.h42
-rw-r--r--device/hid/hid_utils_mac.cc39
-rw-r--r--device/hid/hid_utils_mac.h23
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_