Upstream version 11.39.250.0
[platform/framework/web/crosswalk.git] / src / device / hid / hid_connection_mac.cc
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.
4
5 #include "device/hid/hid_connection_mac.h"
6
7 #include "base/bind.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"
14
15 namespace device {
16
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());
26
27   IOHIDDeviceScheduleWithRunLoop(
28       device_.get(), CFRunLoopGetMain(), kCFRunLoopDefaultMode);
29
30   size_t expected_report_size = device_info.max_input_report_size;
31   if (device_info.has_report_id) {
32     expected_report_size++;
33   }
34   inbound_buffer_.resize(expected_report_size);
35
36   if (inbound_buffer_.size() > 0) {
37     AddRef();  // Hold a reference to this while this callback is registered.
38     IOHIDDeviceRegisterInputReportCallback(
39         device_.get(),
40         &inbound_buffer_[0],
41         inbound_buffer_.size(),
42         &HidConnectionMac::InputReportCallback,
43         this);
44   }
45 }
46
47 HidConnectionMac::~HidConnectionMac() {
48 }
49
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.
55     Release();
56   }
57
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);
64   }
65
66   while (!pending_reads_.empty()) {
67     pending_reads_.front().callback.Run(false, NULL, 0);
68     pending_reads_.pop();
69   }
70 }
71
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);
77   ProcessReadQueue();
78 }
79
80 void HidConnectionMac::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
81                                      size_t size,
82                                      const WriteCallback& callback) {
83   file_task_runner_->PostTask(FROM_HERE,
84                               base::Bind(&HidConnectionMac::SetReportAsync,
85                                          this,
86                                          kIOHIDReportTypeOutput,
87                                          buffer,
88                                          size,
89                                          callback));
90 }
91
92 void HidConnectionMac::PlatformGetFeatureReport(uint8_t report_id,
93                                                 const ReadCallback& callback) {
94   file_task_runner_->PostTask(
95       FROM_HERE,
96       base::Bind(
97           &HidConnectionMac::GetFeatureReportAsync, this, report_id, callback));
98 }
99
100 void HidConnectionMac::PlatformSendFeatureReport(
101     scoped_refptr<net::IOBuffer> buffer,
102     size_t size,
103     const WriteCallback& callback) {
104   file_task_runner_->PostTask(FROM_HERE,
105                               base::Bind(&HidConnectionMac::SetReportAsync,
106                                          this,
107                                          kIOHIDReportTypeFeature,
108                                          buffer,
109                                          size,
110                                          callback));
111 }
112
113 // static
114 void HidConnectionMac::InputReportCallback(void* context,
115                                            IOReturn result,
116                                            void* sender,
117                                            IOHIDReportType type,
118                                            uint32_t report_id,
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);
125     return;
126   }
127
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);
133   } else {
134     buffer = new net::IOBufferWithSize(report_length + 1);
135     buffer->data()[0] = 0;
136     memcpy(buffer->data() + 1, report_bytes, report_length);
137   }
138
139   connection->ProcessInputReport(buffer);
140 }
141
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);
149   ProcessReadQueue();
150 }
151
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();
157
158     pending_reports_.pop();
159     if (CompleteRead(report.buffer, report.size, read.callback)) {
160       pending_reads_.pop();
161     }
162   }
163 }
164
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();
170
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.
176   IOReturn result =
177       IOHIDDeviceGetReport(device_.get(),
178                            kIOHIDReportTypeFeature,
179                            report_id,
180                            reinterpret_cast<uint8_t*>(buffer->data()),
181                            &report_size);
182   if (result == kIOReturnSuccess) {
183     task_runner_->PostTask(
184         FROM_HERE,
185         base::Bind(&HidConnectionMac::ReturnAsyncResult,
186                    this,
187                    base::Bind(callback, true, buffer, report_size)));
188   } else {
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,
193                                       this,
194                                       base::Bind(callback, false, nullptr, 0)));
195   }
196 }
197
198 void HidConnectionMac::SetReportAsync(IOHIDReportType report_type,
199                                       scoped_refptr<net::IOBuffer> buffer,
200                                       size_t size,
201                                       const WriteCallback& callback) {
202   uint8_t* data = reinterpret_cast<uint8_t*>(buffer->data());
203   DCHECK_GE(size, 1u);
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.
208     ++data;
209     --size;
210   }
211
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.
217   IOReturn result =
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,
222                                       this,
223                                       base::Bind(callback, true)));
224   } else {
225     VLOG(1) << "Failed to set report: " << base::StringPrintf("0x%08x", result);
226     task_runner_->PostTask(FROM_HERE,
227                            base::Bind(&HidConnectionMac::ReturnAsyncResult,
228                                       this,
229                                       base::Bind(callback, false)));
230   }
231 }
232
233 void HidConnectionMac::ReturnAsyncResult(const base::Closure& callback) {
234   callback.Run();
235 }
236
237 }  // namespace device