blob: 993ebf4fe0cf6dbd30e621781f23d54b03451747 [file] [log] [blame] [edit]
// Copyright 2018 The Fuchsia 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 "garnet/bin/ui/input_reader/hid_decoder.h"
#include <fcntl.h>
#include <hid/acer12.h>
#include <hid/egalax.h>
#include <hid/paradise.h>
#include <hid/samsung.h>
#include <unistd.h>
#include <zircon/device/device.h>
#include <zircon/device/input.h>
#include <hid-parser/parser.h>
#include <hid-parser/usages.h>
#include "lib/fxl/logging.h"
namespace {
bool log_err(ssize_t rc, const std::string& what, const std::string& name) {
FXL_LOG(ERROR) << "hid: could not get " << what << " from " << name
<< " (status=" << rc << ")";
return false;
}
// Casting from unsigned to signed can change the bit pattern so
// we need to resort to this method.
int8_t signed_bit_cast(uint8_t src) {
int8_t dest;
memcpy(&dest, &src, sizeof(uint8_t));
return dest;
}
// Extracts an int8_t sign extended to int32_t from a byte vector |v|.
// Both |begin| and |count| are in bits units. This function does not
// check for the vector being long enough.
int32_t extract_int(const std::vector<uint8_t>& v, uint32_t begin,
uint32_t count) {
uint8_t val = v[begin / 8u] >> (begin % 8u);
val = (count < 8) ? (val & ~(1u << count)) : val;
return signed_bit_cast(val);
};
} // namespace
namespace mozart {
HidDecoder::HidDecoder(const std::string& name, int fd)
: fd_(fd), name_(name), protocol_(Protocol::Other) {}
bool HidDecoder::Init() {
if (!ParseProtocol(&protocol_))
return false;
input_report_size_t max_len = 0;
ioctl_input_get_max_reportsize(fd_, &max_len);
report_.resize(max_len);
return true;
}
bool HidDecoder::GetEvent(zx_handle_t* handle) {
ssize_t rc = ioctl_device_get_event_handle(fd_, handle);
return (rc < 0) ? log_err(rc, "event handle", name_) : true;
}
bool HidDecoder::ParseProtocol(Protocol* protocol) {
int coarse_protocol;
ssize_t rc = ioctl_input_get_protocol(fd_, &coarse_protocol);
if (rc < 0)
return log_err(rc, "ioctl protocol", name_);
// For most keyboards and mouses Zircon requests the boot protocol
// which has a fixed layout. This covers the following two cases:
if (coarse_protocol == INPUT_PROTO_KBD) {
*protocol = Protocol::Keyboard;
return true;
}
if (coarse_protocol == INPUT_PROTO_MOUSE) {
*protocol = Protocol::Mouse;
return true;
}
// For the rest of devices (INPUT_PROTO_NONE) we need to parse the
// report descriptor. The legacy method involves memcmp() of known
// descriptors which cover the next 8 devices:
size_t report_desc_len;
rc = ioctl_input_get_report_desc_size(fd_, &report_desc_len);
if (rc < 0)
return log_err(rc, "report descriptor length", name_);
std::vector<uint8_t> desc(report_desc_len);
rc = ioctl_input_get_report_desc(fd_, desc.data(), desc.size());
if (rc < 0)
return log_err(rc, "report descriptor", name_);
if (is_acer12_touch_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::Acer12Touch;
return true;
}
if (is_samsung_touch_report_desc(desc.data(), desc.size())) {
setup_samsung_touch(fd_);
*protocol = Protocol::SamsungTouch;
return true;
}
if (is_paradise_touch_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::ParadiseV1Touch;
return true;
}
if (is_paradise_touch_v2_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::ParadiseV2Touch;
return true;
}
if (is_paradise_touchpad_v1_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::ParadiseV1TouchPad;
return true;
}
if (is_paradise_touchpad_v2_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::ParadiseV2TouchPad;
return true;
}
if (is_egalax_touchscreen_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::EgalaxTouch;
return true;
}
if (is_paradise_sensor_report_desc(desc.data(), desc.size())) {
*protocol = Protocol::ParadiseSensor;
return true;
}
// For the rest of devices we use the new way; with the hid-parser
// library.
hid::DeviceDescriptor* dev_desc = nullptr;
auto parse_res =
hid::ParseReportDescriptor(desc.data(), desc.size(), &dev_desc);
if (parse_res != hid::ParseResult::kParseOk) {
FXL_LOG(ERROR) << "hid-parser: error " << int(parse_res)
<< " parsing report descriptor for " << name_;
return false;
}
auto count = dev_desc->rep_count;
if (count == 0) {
FXL_LOG(ERROR) << "no report descriptors for " << name_;
return false;
}
// Find the first input report.
const hid::ReportField* input_fields = nullptr;
size_t field_count = 0;
for (size_t rep = 0; rep < count; rep++) {
const hid::ReportField* fields = dev_desc->report[rep].first_field;
if (fields[0].type == hid::kInput) {
input_fields = fields;
field_count = dev_desc->report[rep].count;
break;
}
}
if (input_fields == nullptr) {
FXL_LOG(ERROR) << "no input report fields for " << name_;
return false;
}
// Traverse up the nested collections to the Application collection.
auto collection = input_fields[0].col;
while (collection != nullptr) {
if (collection->type == hid::CollectionType::kApplication) {
break;
}
collection = collection->parent;
}
if (collection == nullptr) {
FXL_LOG(ERROR) << "invalid hid collection for " << name_;
return false;
}
FXL_LOG(INFO) << "hid-parser succesful for " << name_ << " with usage page "
<< collection->usage.page << " and usage "
<< collection->usage.usage;
if (collection->usage.page == hid::usage::Page::kGenericDesktop) {
if (collection->usage.usage == hid::usage::GenericDesktop::kJoystick) {
// Most modern gamepads report themselves as Joysticks. Madness.
if (ParseGamepadDescriptor(input_fields, field_count))
protocol_ = Protocol::Gamepad;
}
}
*protocol = protocol_;
return true;
}
bool HidDecoder::use_legacy_mode() const {
return protocol_ != Protocol::Gamepad;
}
bool HidDecoder::ParseGamepadDescriptor(const hid::ReportField* fields,
size_t count) {
// Need to recover the five fields as seen in HidGamepadSimple and put
// them into the decoder_ in the same order.
if (count < 5u)
return false;
decoder_.resize(6u);
uint8_t offset = 0;
if (fields[0].report_id != 0) {
// If exists, the first entry (8-bits) is always the report id and
// all items start after the first byte.
decoder_[0] = DataLocator{0u, 8u, fields[0].report_id};
offset = 8u;
}
// Needs to be kept in sync with HidGamepadSimple {}.
const uint16_t table[] = {
static_cast<uint16_t>(hid::usage::GenericDesktop::kX), // left X.
static_cast<uint16_t>(hid::usage::GenericDesktop::kY), // left Y.
static_cast<uint16_t>(hid::usage::GenericDesktop::kZ), // right X.
static_cast<uint16_t>(hid::usage::GenericDesktop::kRz), // right Y.
static_cast<uint16_t>(hid::usage::GenericDesktop::kHatSwitch) // buttons
};
uint32_t bit_count = 0;
// Traverse each input report field and see if there is a match in the table.
// If so place the location in |decoder_| array.
for (size_t ix = 0; ix != count; ix++) {
if (fields[ix].type != hid::kInput)
continue;
for (size_t iy = 0; iy != countof(table); iy++) {
if (fields[ix].attr.usage.usage == table[iy]) {
// Found a required usage.
decoder_[iy + 1] =
DataLocator{bit_count + offset, fields[ix].attr.bit_sz, 0};
break;
}
}
bit_count += fields[ix].attr.bit_sz;
}
// Here |decoder_| should look like this:
// [rept_id][left X][left Y]....[hat_sw]
// With each box, the location in a report for each item, for example:
// [0, 0, 0][24, 0, 0][8, 0, 0][0, 0, 0]...[64, 4, 0]
return true;
}
const std::vector<uint8_t>& HidDecoder::Read(int* bytes_read) {
*bytes_read = read(fd_, report_.data(), report_.size());
return report_;
}
bool HidDecoder::Read(HidGamepadSimple* gamepad) {
if (protocol_ != Protocol::Gamepad)
return false;
int rc;
auto report = Read(&rc);
if (rc < 1) {
FXL_LOG(ERROR) << "Failed to read from input: " << rc;
return false;
}
auto cur = &decoder_[0];
if ((cur->match != 0) && (cur->count == 8u)) {
// The first byte is the report id.
if (report[0] != cur->match) {
// This is a normal condition. The device can generate reports
// for controls we don't yet handle.
*gamepad = {};
return true;
}
++cur;
}
gamepad->left_x = extract_int(report, cur->begin, cur->count) / 2;
++cur;
gamepad->left_y = extract_int(report, cur->begin, cur->count) / 2;
++cur;
gamepad->right_x = extract_int(report, cur->begin, cur->count) / 2;
++cur;
gamepad->right_y = extract_int(report, cur->begin, cur->count) / 2;
++cur;
gamepad->hat_switch = extract_int(report, cur->begin, cur->count);
return true;
}
} // namespace mozart