| // Copyright 2016 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/input_interpreter.h" |
| |
| #include <fuchsia/ui/input/cpp/fidl.h> |
| #include <hid-parser/parser.h> |
| #include <hid-parser/report.h> |
| #include <hid-parser/usages.h> |
| #include <lib/fidl/cpp/clone.h> |
| #include <lib/ui/input/cpp/formatting.h> |
| #include <src/lib/fxl/arraysize.h> |
| #include <src/lib/fxl/logging.h> |
| #include <src/lib/fxl/time/time_point.h> |
| #include <sys/types.h> |
| #include <sys/uio.h> |
| #include <trace/event.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include "garnet/bin/ui/input_reader/device.h" |
| #include "garnet/bin/ui/input_reader/fdio_hid_decoder.h" |
| #include "garnet/bin/ui/input_reader/protocols.h" |
| |
| namespace { |
| |
| int64_t InputEventTimestampNow() { |
| return fxl::TimePoint::Now().ToEpochDelta().ToNanoseconds(); |
| } |
| |
| fuchsia::ui::input::InputReport CloneReport( |
| const fuchsia::ui::input::InputReport& report) { |
| fuchsia::ui::input::InputReport result; |
| fidl::Clone(report, &result); |
| return result; |
| } |
| |
| } // namespace |
| |
| namespace ui_input { |
| |
| InputInterpreter::InputInterpreter( |
| std::unique_ptr<HidDecoder> hid_decoder, |
| fuchsia::ui::input::InputDeviceRegistry* registry) |
| : registry_(registry), hid_decoder_(std::move(hid_decoder)) { |
| FXL_DCHECK(hid_decoder_); |
| } |
| |
| InputInterpreter::~InputInterpreter() {} |
| |
| bool InputInterpreter::Initialize() { |
| if (!hid_decoder_->Init()) |
| return false; |
| |
| if (!ParseProtocol()) |
| return false; |
| |
| hardcoded_.Initialize(protocol_); |
| |
| event_ = hid_decoder_->GetEvent(); |
| if (!event_) |
| return false; |
| |
| NotifyRegistry(); |
| |
| for (size_t i = 0; i < devices_.size(); i++) { |
| InputDevice& device = devices_[i]; |
| // If we are a media button then query for an initial report. |
| if (device.descriptor.protocol == Protocol::MediaButtons) { |
| std::vector<uint8_t> initial_input; |
| zx_status_t status = |
| hid_decoder_->GetReport(HidDecoder::ReportType::INPUT, |
| device.device->ReportId(), &initial_input); |
| if (status != ZX_OK) { |
| return false; |
| } |
| if (device.device->ParseReport(initial_input.data(), initial_input.size(), |
| device.report.get())) { |
| device.report->event_time = InputEventTimestampNow(); |
| device.report->trace_id = TRACE_NONCE(); |
| TRACE_FLOW_BEGIN("input", "hid_read_to_listener", |
| device.report->trace_id); |
| device.input_device->DispatchReport(CloneReport(*device.report)); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| void InputInterpreter::NotifyRegistry() { |
| hardcoded_.NotifyRegistry(registry_); |
| |
| // Register the generic device's descriptors. |
| for (size_t i = 0; i < devices_.size(); i++) { |
| fuchsia::ui::input::DeviceDescriptor descriptor; |
| InputDevice& device = devices_[i]; |
| if (device.descriptor.has_keyboard) { |
| fidl::Clone(device.descriptor.keyboard_descriptor, &descriptor.keyboard); |
| } |
| if (device.descriptor.has_mouse) { |
| fidl::Clone(device.descriptor.mouse_descriptor, &descriptor.mouse); |
| } |
| if (device.descriptor.has_stylus) { |
| fidl::Clone(device.descriptor.stylus_descriptor, &descriptor.stylus); |
| } |
| if (device.descriptor.has_touchscreen) { |
| fidl::Clone(device.descriptor.touchscreen_descriptor, |
| &descriptor.touchscreen); |
| } |
| if (device.descriptor.has_sensor) { |
| fidl::Clone(device.descriptor.sensor_descriptor, &descriptor.sensor); |
| } |
| if (device.descriptor.has_media_buttons) { |
| fidl::Clone(device.descriptor.buttons_descriptor, |
| &descriptor.media_buttons); |
| } |
| registry_->RegisterDevice(std::move(descriptor), |
| device.input_device.NewRequest()); |
| } |
| } |
| |
| bool InputInterpreter::Read(bool discard) { |
| TRACE_DURATION("input", "hid_read"); |
| |
| // If positive |rc| is the number of bytes read. If negative the error |
| // while reading. |
| int rc = 1; |
| auto report = hid_decoder_->Read(&rc); |
| |
| if (rc < 1) { |
| FXL_LOG(ERROR) << "Failed to read from input: " << rc << " for " << name(); |
| // TODO(cpu) check whether the device was actually closed or not. |
| return false; |
| } |
| |
| hardcoded_.Read(report, rc, discard); |
| |
| for (size_t i = 0; i < devices_.size(); i++) { |
| InputDevice& device = devices_[i]; |
| if (device.device->ReportId() != 0 && |
| device.device->ReportId() != report[0]) { |
| continue; |
| } |
| if (device.device->ParseReport(report.data(), rc, device.report.get())) { |
| if (!discard) { |
| device.report->event_time = InputEventTimestampNow(); |
| device.report->trace_id = TRACE_NONCE(); |
| TRACE_FLOW_BEGIN("input", "hid_read_to_listener", |
| device.report->trace_id); |
| device.input_device->DispatchReport(CloneReport(*device.report)); |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| Protocol InputInterpreter::ExtractProtocol(hid::Usage input) { |
| using ::hid::usage::Consumer; |
| using ::hid::usage::Digitizer; |
| using ::hid::usage::GenericDesktop; |
| using ::hid::usage::Page; |
| using ::hid::usage::Sensor; |
| struct { |
| hid::Usage usage; |
| Protocol protocol; |
| } usage_to_protocol[] = { |
| {{static_cast<uint16_t>(Page::kConsumer), |
| static_cast<uint32_t>(Consumer::kConsumerControl)}, |
| Protocol::MediaButtons}, |
| {{static_cast<uint16_t>(Page::kDigitizer), |
| static_cast<uint32_t>(Digitizer::kTouchScreen)}, |
| Protocol::Touch}, |
| {{static_cast<uint16_t>(Page::kDigitizer), |
| static_cast<uint32_t>(Digitizer::kTouchPad)}, |
| Protocol::Touchpad}, |
| {{static_cast<uint16_t>(Page::kDigitizer), |
| static_cast<uint32_t>(Digitizer::kStylus)}, |
| Protocol::Stylus}, |
| {{static_cast<uint16_t>(Page::kDigitizer), |
| static_cast<uint32_t>(Digitizer::kPen)}, |
| Protocol::Stylus}, |
| {{static_cast<uint16_t>(Page::kGenericDesktop), |
| static_cast<uint32_t>(GenericDesktop::kMouse)}, |
| Protocol::Mouse}, |
| {{static_cast<uint16_t>(Page::kGenericDesktop), |
| static_cast<uint32_t>(GenericDesktop::kPointer)}, |
| Protocol::Pointer}, |
| // Add more sensors here |
| }; |
| |
| if (input.page == Page::kSensor) { |
| return Protocol::Sensor; |
| } |
| |
| for (auto& j : usage_to_protocol) { |
| if (input.page == j.usage.page && input.usage == j.usage.usage) { |
| return j.protocol; |
| } |
| } |
| return Protocol::Other; |
| } |
| |
| bool InputInterpreter::ParseHidFeatureReportDescriptor( |
| const hid::ReportDescriptor& report_desc) { |
| // Traverse up the nested collections to the Application collection. |
| auto collection = report_desc.input_fields[0].col; |
| while (collection != nullptr) { |
| if (collection->type == hid::CollectionType::kApplication) { |
| break; |
| } |
| collection = collection->parent; |
| } |
| |
| if (collection == nullptr) { |
| FXL_LOG(INFO) << "Can't process HID feature report descriptor for " |
| << name() << "; Needed a valid Collection but didn't get one"; |
| return false; |
| } |
| |
| // If we have a touchscreen feature report then we enable multitouch mode. |
| if (collection->usage == |
| hid::USAGE(hid::usage::Page::kDigitizer, |
| hid::usage::Digitizer::kTouchScreenConfiguration)) { |
| std::vector<uint8_t> feature_report(report_desc.feature_byte_sz); |
| if (report_desc.report_id != 0) { |
| feature_report[0] = report_desc.report_id; |
| } |
| for (size_t i = 0; i < report_desc.feature_count; i++) { |
| const hid::ReportField& field = report_desc.input_fields[i]; |
| if (field.attr.usage == |
| hid::USAGE(hid::usage::Page::kDigitizer, |
| hid::usage::Digitizer::kTouchScreenInputMode)) { |
| InsertUint(feature_report.data(), feature_report.size(), field.attr, |
| static_cast<uint32_t>( |
| hid::usage::TouchScreenInputMode::kMultipleInput)); |
| } |
| } |
| hid_decoder_->Send(HidDecoder::ReportType::FEATURE, report_desc.report_id, |
| feature_report); |
| } |
| return true; |
| } |
| |
| bool InputInterpreter::ParseHidInputReportDescriptor( |
| const hid::ReportDescriptor* input_desc) { |
| FXL_CHECK(input_desc); |
| |
| // Traverse up the nested collections to the Application collection. |
| hid::Collection* collection = input_desc->input_fields[0].col; |
| while (collection != nullptr) { |
| if (collection->type == hid::CollectionType::kApplication) { |
| break; |
| } |
| collection = collection->parent; |
| } |
| |
| if (collection == nullptr) { |
| FXL_LOG(INFO) << "Can't process HID report descriptor for " << name() |
| << "; Needed a valid Collection but didn't get one"; |
| return false; |
| } |
| |
| InputDevice input_device = {}; |
| input_device.report = fuchsia::ui::input::InputReport::New(); |
| |
| // Most modern gamepads report themselves as Joysticks. Madness. |
| if (collection->usage.page == hid::usage::Page::kGenericDesktop && |
| collection->usage.usage == hid::usage::GenericDesktop::kJoystick && |
| hardcoded_.ParseGamepadDescriptor(input_desc->input_fields, |
| input_desc->input_count)) { |
| protocol_ = Protocol::Gamepad; |
| return true; |
| } else { |
| protocol_ = ExtractProtocol(collection->usage); |
| switch (protocol_) { |
| case Protocol::LightSensor: |
| hardcoded_.ParseAmbientLightDescriptor(input_desc->input_fields, |
| input_desc->input_count); |
| return true; |
| case Protocol::MediaButtons: { |
| FXL_VLOG(2) << "Device " << name() << " has HID media buttons"; |
| |
| input_device.device = std::make_unique<Buttons>(); |
| input_device.report->media_buttons = |
| fuchsia::ui::input::MediaButtonsReport::New(); |
| break; |
| } |
| case Protocol::Pointer: { |
| FXL_VLOG(2) << "Device " << name() << " has HID pointer"; |
| |
| input_device.device = std::make_unique<Pointer>(); |
| input_device.report->touchscreen = |
| fuchsia::ui::input::TouchscreenReport::New(); |
| break; |
| } |
| case Protocol::Sensor: { |
| FXL_VLOG(2) << "Device " << name() << " has HID sensor"; |
| |
| input_device.device = std::make_unique<Sensor>(); |
| input_device.report->sensor = fuchsia::ui::input::SensorReport::New(); |
| break; |
| } |
| case Protocol::Touchpad: { |
| FXL_VLOG(2) << "Device " << name() << " has HID touchpad"; |
| |
| input_device.device = std::make_unique<Touchpad>(); |
| input_device.report->mouse = fuchsia::ui::input::MouseReport::New(); |
| break; |
| } |
| case Protocol::Touch: { |
| FXL_VLOG(2) << "Device " << name() << " has HID touch"; |
| |
| input_device.device = std::make_unique<TouchScreen>(); |
| input_device.report->touchscreen = |
| fuchsia::ui::input::TouchscreenReport::New(); |
| break; |
| } |
| case Protocol::Mouse: { |
| FXL_VLOG(2) << "Device " << name() << " has HID mouse"; |
| |
| input_device.device = std::make_unique<Mouse>(); |
| input_device.report->mouse = fuchsia::ui::input::MouseReport::New(); |
| break; |
| } |
| case Protocol::Stylus: { |
| FXL_VLOG(2) << "Device " << name() << " has HID stylus"; |
| |
| input_device.device = std::make_unique<Stylus>(); |
| input_device.report->stylus = fuchsia::ui::input::StylusReport::New(); |
| break; |
| } |
| // Add more protocols here |
| default: |
| // Not being able to match on a given HID report descriptor is not |
| // an error and will happen frequently. We only need to match a single |
| // report in the report descriptor to be valid. |
| return true; |
| } |
| } |
| |
| if (!input_device.device->ParseReportDescriptor(*input_desc, |
| &input_device.descriptor)) { |
| FXL_LOG(INFO) << "Can't process HID report descriptor for " << name() |
| << "; Failed to do generic device parsing"; |
| return false; |
| } |
| devices_.push_back(std::move(input_device)); |
| |
| FXL_LOG(INFO) << "hid-parser successful for " << name() << " with usage page " |
| << collection->usage.page << " and usage " |
| << collection->usage.usage; |
| |
| return true; |
| } |
| |
| bool InputInterpreter::ParseProtocol() { |
| HidDecoder::BootMode boot_mode = hid_decoder_->ReadBootMode(); |
| // For most keyboards and mouses Zircon requests the boot protocol |
| // which has a fixed layout. This covers the following two cases: |
| if (boot_mode == HidDecoder::BootMode::KEYBOARD) { |
| protocol_ = Protocol::Keyboard; |
| return true; |
| } |
| if (boot_mode == HidDecoder::BootMode::MOUSE) { |
| protocol_ = Protocol::BootMouse; |
| return true; |
| } |
| |
| int desc_size; |
| auto desc = hid_decoder_->ReadReportDescriptor(&desc_size); |
| if (desc_size == 0) { |
| return false; |
| } |
| |
| // See if we match a hardcoded descriptor. This involves memcmp() of the |
| // known hardcoded descriptors. |
| Protocol protocol = hardcoded_.MatchProtocol(desc, hid_decoder_.get()); |
| if (protocol != Protocol::Other) { |
| protocol_ = protocol; |
| 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(INFO) << "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; |
| } |
| |
| // Parse each input report. |
| for (size_t rep = 0; rep < count; rep++) { |
| const hid::ReportDescriptor* desc = &dev_desc->report[rep]; |
| if (desc->input_count != 0) { |
| if (!ParseHidInputReportDescriptor(desc)) { |
| continue; |
| } |
| } |
| if (desc->feature_count != 0) { |
| if (!ParseHidFeatureReportDescriptor(*desc)) { |
| return false; |
| } |
| } |
| } |
| |
| // If we never parsed a single device correctly then fail. |
| if (devices_.size() == 0) { |
| FXL_LOG(INFO) << "Can't process HID report descriptor for " << name() |
| << "; All parsing attempts failed."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace ui_input |