// 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/mac/foundation_util.h"
#include "base/message_loop/message_loop.h"
#include "base/threading/thread_restrictions.h"
#include "device/hid/hid_connection_mac.h"
namespace device {
HidConnectionMac::HidConnectionMac(HidDeviceInfo device_info)
: HidConnection(device_info),
device_(device_info.device_id, base::scoped_policy::RETAIN) {
DCHECK(thread_checker_.CalledOnValidThread());
message_loop_ = base::MessageLoopProxy::current();
DCHECK(device_.get());
inbound_buffer_.reset((uint8_t*)malloc(device_info.input_report_size));
IOHIDDeviceRegisterInputReportCallback(device_.get(),
inbound_buffer_.get(),
device_info.input_report_size,
&HidConnectionMac::InputReportCallback,
this);
IOHIDDeviceOpen(device_, kIOHIDOptionsTypeNone);
}
HidConnectionMac::~HidConnectionMac() {
DCHECK(thread_checker_.CalledOnValidThread());
while (!pending_reads_.empty()) {
pending_reads_.front().callback.Run(false, 0);
pending_reads_.pop();
}
IOHIDDeviceClose(device_, kIOHIDOptionsTypeNone);
}
void HidConnectionMac::Read(scoped_refptr<net::IOBufferWithSize> buffer,
const IOCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!device_) {
callback.Run(false, 0);
return;
}
PendingHidRead read;
read.buffer = buffer;
read.callback = callback;
pending_reads_.push(read);
ProcessReadQueue();
}
void HidConnectionMac::Write(uint8_t report_id,
scoped_refptr<net::IOBufferWithSize> buffer,
const IOCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
WriteReport(kIOHIDReportTypeOutput, report_id, buffer, callback);
}
void HidConnectionMac::GetFeatureReport(
uint8_t report_id,
scoped_refptr<net::IOBufferWithSize> buffer,
const IOCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (device_info().feature_report_size == 0) {
callback.Run(false, 0);
return;
}
if (buffer->size() < device_info().feature_report_size) {
callback.Run(false, 0);
return;
}
uint8_t* feature_report_buffer = reinterpret_cast<uint8_t*>(buffer->data());
CFIndex feature_report_size = device_info().feature_report_size;
IOReturn result = IOHIDDeviceGetReport(device_,
kIOHIDReportTypeFeature,
report_id,
feature_report_buffer,
&feature_report_size);
if (result == kIOReturnSuccess)
callback.Run(true, feature_report_size);
else
callback.Run(false, 0);
}
void HidConnectionMac::SendFeatureReport(
uint8_t report_id,
scoped_refptr<net::IOBufferWithSize> buffer,
const IOCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
WriteReport(kIOHIDReportTypeFeature, report_id, buffer, callback);
}
void HidConnectionMac::InputReportCallback(void* context,
IOReturn result,
void* sender,
IOHIDReportType type,
uint32_t report_id,
uint8_t* report_bytes,
CFIndex report_length) {
HidConnectionMac* connection = static_cast<HidConnectionMac*>(context);
// report_id is already contained in report_bytes
scoped_refptr<net::IOBufferWithSize> buffer;
buffer = new net::IOBufferWithSize(report_length);
memcpy(buffer->data(), report_bytes, report_length);
connection->message_loop_->PostTask(
FROM_HERE,
base::Bind(
&HidConnectionMac::ProcessInputReport, connection, type, buffer));
}
void HidConnectionMac::ProcessReadQueue() {
DCHECK(thread_checker_.CalledOnValidThread());
while (pending_reads_.size() && pending_reports_.size()) {
PendingHidRead read = pending_reads_.front();
pending_reads_.pop();
PendingHidReport report = pending_reports_.front();
if (read.buffer->size() < report.buffer->size()) {
read.callback.Run(false, report.buffer->size());
} else {
memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size());
pending_reports_.pop();
read.callback.Run(true, report.buffer->size());
}
}
}
void HidConnectionMac::ProcessInputReport(
IOHIDReportType type,
scoped_refptr<net::IOBufferWithSize> buffer) {
DCHECK(thread_checker_.CalledOnValidThread());
PendingHidReport report;
report.buffer = buffer;
pending_reports_.push(report);
ProcessReadQueue();
}
void HidConnectionMac::WriteReport(IOHIDReportType type,
uint8_t report_id,
scoped_refptr<net::IOBufferWithSize> buffer,
const IOCallback& callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!device_) {
callback.Run(false, 0);
return;
}
scoped_refptr<net::IOBufferWithSize> output_buffer;
if (report_id != 0) {
output_buffer = new net::IOBufferWithSize(buffer->size() + 1);
output_buffer->data()[0] = static_cast<uint8_t>(report_id);
memcpy(output_buffer->data() + 1, buffer->data(), buffer->size());
} else {
output_buffer = new net::IOBufferWithSize(buffer->size());
memcpy(output_buffer->data(), buffer->data(), buffer->size());
}
IOReturn res =
IOHIDDeviceSetReport(device_.get(),
type,
report_id,
reinterpret_cast<uint8_t*>(output_buffer->data()),
output_buffer->size());
if (res != kIOReturnSuccess) {
callback.Run(false, 0);
} else {
callback.Run(true, output_buffer->size());
}
}
} // namespace device