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 "components/device_event_log/device_event_log.h"
14 #include "device/hid/hid_connection_mac.h"
20 std::string
HexErrorCode(IOReturn error_code
) {
21 return base::StringPrintf("0x%04x", error_code
);
26 HidConnectionMac::HidConnectionMac(
27 IOHIDDeviceRef device
,
28 scoped_refptr
<HidDeviceInfo
> device_info
,
29 scoped_refptr
<base::SingleThreadTaskRunner
> file_task_runner
)
30 : HidConnection(device_info
),
31 device_(device
, base::scoped_policy::RETAIN
),
32 file_task_runner_(file_task_runner
) {
33 task_runner_
= base::ThreadTaskRunnerHandle::Get();
34 DCHECK(task_runner_
.get());
36 IOHIDDeviceScheduleWithRunLoop(
37 device_
.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
39 size_t expected_report_size
= device_info
->max_input_report_size();
40 if (device_info
->has_report_id()) {
41 expected_report_size
++;
43 inbound_buffer_
.resize(expected_report_size
);
45 if (inbound_buffer_
.size() > 0) {
46 AddRef(); // Hold a reference to this while this callback is registered.
47 IOHIDDeviceRegisterInputReportCallback(
50 inbound_buffer_
.size(),
51 &HidConnectionMac::InputReportCallback
,
56 HidConnectionMac::~HidConnectionMac() {
59 void HidConnectionMac::PlatformClose() {
60 if (inbound_buffer_
.size() > 0) {
61 IOHIDDeviceRegisterInputReportCallback(
62 device_
.get(), &inbound_buffer_
[0], inbound_buffer_
.size(), NULL
, this);
63 // Release the reference taken when this callback was registered.
67 IOHIDDeviceUnscheduleFromRunLoop(
68 device_
.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode
);
69 IOReturn result
= IOHIDDeviceClose(device_
.get(), 0);
70 if (result
!= kIOReturnSuccess
) {
71 HID_LOG(EVENT
) << "Failed to close HID device: " << HexErrorCode(result
);
74 while (!pending_reads_
.empty()) {
75 pending_reads_
.front().callback
.Run(false, NULL
, 0);
80 void HidConnectionMac::PlatformRead(const ReadCallback
& callback
) {
81 DCHECK(thread_checker().CalledOnValidThread());
82 PendingHidRead pending_read
;
83 pending_read
.callback
= callback
;
84 pending_reads_
.push(pending_read
);
88 void HidConnectionMac::PlatformWrite(scoped_refptr
<net::IOBuffer
> buffer
,
90 const WriteCallback
& callback
) {
91 file_task_runner_
->PostTask(FROM_HERE
,
92 base::Bind(&HidConnectionMac::SetReportAsync
,
94 kIOHIDReportTypeOutput
,
100 void HidConnectionMac::PlatformGetFeatureReport(uint8_t report_id
,
101 const ReadCallback
& callback
) {
102 file_task_runner_
->PostTask(
105 &HidConnectionMac::GetFeatureReportAsync
, this, report_id
, callback
));
108 void HidConnectionMac::PlatformSendFeatureReport(
109 scoped_refptr
<net::IOBuffer
> buffer
,
111 const WriteCallback
& callback
) {
112 file_task_runner_
->PostTask(FROM_HERE
,
113 base::Bind(&HidConnectionMac::SetReportAsync
,
115 kIOHIDReportTypeFeature
,
122 void HidConnectionMac::InputReportCallback(void* context
,
125 IOHIDReportType type
,
127 uint8_t* report_bytes
,
128 CFIndex report_length
) {
129 HidConnectionMac
* connection
= static_cast<HidConnectionMac
*>(context
);
130 if (result
!= kIOReturnSuccess
) {
131 HID_LOG(EVENT
) << "Failed to read input report: " << HexErrorCode(result
);
135 scoped_refptr
<net::IOBufferWithSize
> buffer
;
136 if (connection
->device_info()->has_report_id()) {
137 // report_id is already contained in report_bytes
138 buffer
= new net::IOBufferWithSize(report_length
);
139 memcpy(buffer
->data(), report_bytes
, report_length
);
141 buffer
= new net::IOBufferWithSize(report_length
+ 1);
142 buffer
->data()[0] = 0;
143 memcpy(buffer
->data() + 1, report_bytes
, report_length
);
146 connection
->ProcessInputReport(buffer
);
149 void HidConnectionMac::ProcessInputReport(
150 scoped_refptr
<net::IOBufferWithSize
> buffer
) {
151 DCHECK(thread_checker().CalledOnValidThread());
152 PendingHidReport report
;
153 report
.buffer
= buffer
;
154 report
.size
= buffer
->size();
155 pending_reports_
.push(report
);
159 void HidConnectionMac::ProcessReadQueue() {
160 DCHECK(thread_checker().CalledOnValidThread());
161 while (pending_reads_
.size() && pending_reports_
.size()) {
162 PendingHidRead read
= pending_reads_
.front();
163 PendingHidReport report
= pending_reports_
.front();
165 pending_reports_
.pop();
166 if (CompleteRead(report
.buffer
, report
.size
, read
.callback
)) {
167 pending_reads_
.pop();
172 void HidConnectionMac::GetFeatureReportAsync(uint8_t report_id
,
173 const ReadCallback
& callback
) {
174 scoped_refptr
<net::IOBufferWithSize
> buffer(
175 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1));
176 CFIndex report_size
= buffer
->size();
178 // The IOHIDDevice object is shared with the UI thread and so this function
179 // should probably be called there but it may block and the asynchronous
180 // version is NOT IMPLEMENTED. I've examined the open source implementation
181 // of this function and believe it is a simple enough wrapper around the
182 // kernel API that this is safe.
184 IOHIDDeviceGetReport(device_
.get(),
185 kIOHIDReportTypeFeature
,
187 reinterpret_cast<uint8_t*>(buffer
->data()),
189 if (result
== kIOReturnSuccess
) {
190 task_runner_
->PostTask(
192 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
194 base::Bind(callback
, true, buffer
, report_size
)));
196 HID_LOG(EVENT
) << "Failed to get feature report: " << HexErrorCode(result
);
197 task_runner_
->PostTask(FROM_HERE
,
198 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
200 base::Bind(callback
, false, nullptr, 0)));
204 void HidConnectionMac::SetReportAsync(IOHIDReportType report_type
,
205 scoped_refptr
<net::IOBuffer
> buffer
,
207 const WriteCallback
& callback
) {
208 uint8_t* data
= reinterpret_cast<uint8_t*>(buffer
->data());
210 uint8_t report_id
= data
[0];
211 if (report_id
== 0) {
212 // OS X only expects the first byte of the buffer to be the report ID if the
213 // report ID is non-zero.
218 // The IOHIDDevice object is shared with the UI thread and so this function
219 // should probably be called there but it may block and the asynchronous
220 // version is NOT IMPLEMENTED. I've examined the open source implementation
221 // of this function and believe it is a simple enough wrapper around the
222 // kernel API that this is safe.
224 IOHIDDeviceSetReport(device_
.get(), report_type
, report_id
, data
, size
);
225 if (result
== kIOReturnSuccess
) {
226 task_runner_
->PostTask(FROM_HERE
,
227 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
229 base::Bind(callback
, true)));
231 HID_LOG(EVENT
) << "Failed to set report: " << HexErrorCode(result
);
232 task_runner_
->PostTask(FROM_HERE
,
233 base::Bind(&HidConnectionMac::ReturnAsyncResult
,
235 base::Bind(callback
, false)));
239 void HidConnectionMac::ReturnAsyncResult(const base::Closure
& callback
) {
243 } // namespace device