1 // Copyright (c) 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "device/hid/hid_connection_mac.h"
8 #include "base/location.h"
9 #include "base/mac/foundation_util.h"
10 #include "base/single_thread_task_runner.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/thread_task_runner_handle.h"
13 #include "device/hid/hid_connection_mac.h"
17 HidConnectionMac::HidConnectionMac(
18 IOHIDDeviceRef device
,
19 HidDeviceInfo device_info
,
20 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
)
21 : HidConnection(device_info
),
22 device_(device
, base::scoped_policy::RETAIN
),
23 file_task_runner_(file_task_runner
) {
24 task_runner_
= base::ThreadTaskRunnerHandle::Get();
25 DCHECK(task_runner_
.get());
27 IOHIDDeviceScheduleWithRunLoop(
28 device_
.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
30 size_t expected_report_size
= device_info
.max_input_report_size
;
31 if (device_info
.has_report_id
) {
32 expected_report_size
++;
34 inbound_buffer_
.resize(expected_report_size
);
36 if (inbound_buffer_
.size() > 0) {
37 AddRef(); // Hold a reference to this while this callback is registered.
38 IOHIDDeviceRegisterInputReportCallback(
41 inbound_buffer_
.size(),
42 &HidConnectionMac::InputReportCallback
,
47 HidConnectionMac::~HidConnectionMac() {
50 void HidConnectionMac::PlatformClose() {
51 if (inbound_buffer_
.size() > 0) {
52 IOHIDDeviceRegisterInputReportCallback(
53 device_
.get(), &inbound_buffer_
[0], inbound_buffer_
.size(), NULL
, this);
54 // Release the reference taken when this callback was registered.
58 IOHIDDeviceUnscheduleFromRunLoop(
59 device_
.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
60 IOReturn result
= IOHIDDeviceClose(device_
.get(), 0);
61 if (result
!= kIOReturnSuccess
) {
62 VLOG(1) << "Failed to close HID device: "
63 << base::StringPrintf("0x%04x", result
);
66 while (!pending_reads_
.empty()) {
67 pending_reads_
.front().callback
.Run(false, NULL
, 0);
72 void HidConnectionMac::PlatformRead(const ReadCallback
& callback
) {
73 DCHECK(thread_checker().CalledOnValidThread());
74 PendingHidRead pending_read
;
75 pending_read
.callback
= callback
;
76 pending_reads_
.push(pending_read
);
80 void HidConnectionMac::PlatformWrite(scoped_refptr
<net::IOBuffer
> buffer
,
82 const WriteCallback
& callback
) {
83 file_task_runner_
->PostTask(FROM_HERE
,
84 base::Bind(&HidConnectionMac::SetReportAsync
,
86 kIOHIDReportTypeOutput
,
92 void HidConnectionMac::PlatformGetFeatureReport(uint8_t report_id
,
93 const ReadCallback
& callback
) {
94 file_task_runner_
->PostTask(
97 &HidConnectionMac::GetFeatureReportAsync
, this, report_id
, callback
));
100 void HidConnectionMac::PlatformSendFeatureReport(
101 scoped_refptr
<net::IOBuffer
> buffer
,
103 const WriteCallback
& callback
) {
104 file_task_runner_
->PostTask(FROM_HERE
,
105 base::Bind(&HidConnectionMac::SetReportAsync
,
107 kIOHIDReportTypeFeature
,
114 void HidConnectionMac::InputReportCallback(void* context
,
117 IOHIDReportType type
,
119 uint8_t* report_bytes
,
120 CFIndex report_length
) {
121 HidConnectionMac
* connection
= static_cast<HidConnectionMac
*>(context
);
122 if (result
!= kIOReturnSuccess
) {
123 VLOG(1) << "Failed to read input report: "
124 << base::StringPrintf("0x%08x", result
);
128 scoped_refptr
<net::IOBufferWithSize
> buffer
;
129 if (connection
->device_info().has_report_id
) {
130 // report_id is already contained in report_bytes
131 buffer
= new net::IOBufferWithSize(report_length
);
132 memcpy(buffer
->data(), report_bytes
, report_length
);
134 buffer
= new net::IOBufferWithSize(report_length
+ 1);
135 buffer
->data()[0] = 0;
136 memcpy(buffer
->data() + 1, report_bytes
, report_length
);
139 connection
->ProcessInputReport(buffer
);
142 void HidConnectionMac::ProcessInputReport(
143 scoped_refptr
<net::IOBufferWithSize
> buffer
) {
144 DCHECK(thread_checker().CalledOnValidThread());
145 PendingHidReport report
;
146 report
.buffer
= buffer
;
147 report
.size
= buffer
->size();
148 pending_reports_
.push(report
);
152 void HidConnectionMac::ProcessReadQueue() {
153 DCHECK(thread_checker().CalledOnValidThread());
154 while (pending_reads_
.size() && pending_reports_
.size()) {
155 PendingHidRead read
= pending_reads_
.front();
156 PendingHidReport report
= pending_reports_
.front();
158 pending_reports_
.pop();
159 if (CompleteRead(report
.buffer
, report
.size
, read
.callback
)) {
160 pending_reads_
.pop();
165 void HidConnectionMac::GetFeatureReportAsync(uint8_t report_id
,
166 const ReadCallback
& callback
) {
167 scoped_refptr
<net::IOBufferWithSize
> buffer(
168 new net::IOBufferWithSize(device_info().max_feature_report_size
+ 1));
169 CFIndex report_size
= buffer
->size();
171 // The IOHIDDevice object is shared with the UI thread and so this function
172 // should probably be called there but it may block and the asynchronous
173 // version is NOT IMPLEMENTED. I've examined the open source implementation
174 // of this function and believe it is a simple enough wrapper around the
175 // kernel API that this is safe.
177 IOHIDDeviceGetReport(device_
.get(),
178 kIOHIDReportTypeFeature
,
180 reinterpret_cast<uint8_t*>(buffer
->data()),
182 if (result
== kIOReturnSuccess
) {
183 task_runner_
->PostTask(
185 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
187 base::Bind(callback
, true, buffer
, report_size
)));
189 VLOG(1) << "Failed to get feature report: "
190 << base::StringPrintf("0x%08x", result
);
191 task_runner_
->PostTask(FROM_HERE
,
192 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
194 base::Bind(callback
, false, nullptr, 0)));
198 void HidConnectionMac::SetReportAsync(IOHIDReportType report_type
,
199 scoped_refptr
<net::IOBuffer
> buffer
,
201 const WriteCallback
& callback
) {
202 uint8_t* data
= reinterpret_cast<uint8_t*>(buffer
->data());
204 uint8_t report_id
= data
[0];
205 if (report_id
== 0) {
206 // OS X only expects the first byte of the buffer to be the report ID if the
207 // report ID is non-zero.
212 // The IOHIDDevice object is shared with the UI thread and so this function
213 // should probably be called there but it may block and the asynchronous
214 // version is NOT IMPLEMENTED. I've examined the open source implementation
215 // of this function and believe it is a simple enough wrapper around the
216 // kernel API that this is safe.
218 IOHIDDeviceSetReport(device_
.get(), report_type
, report_id
, data
, size
);
219 if (result
== kIOReturnSuccess
) {
220 task_runner_
->PostTask(FROM_HERE
,
221 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
223 base::Bind(callback
, true)));
225 VLOG(1) << "Failed to set report: " << base::StringPrintf("0x%08x", result
);
226 task_runner_
->PostTask(FROM_HERE
,
227 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
229 base::Bind(callback
, false)));
233 void HidConnectionMac::ReturnAsyncResult(const base::Closure
& callback
) {
237 } // namespace device