blob: c78e01f132adbe0591f66dbc14647bf9ed7d7978 [file] [log] [blame]
// 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/hardware/input/c/fidl.h>
#include <fuchsia/ui/input/cpp/fidl.h>
#include <hid-parser/parser.h>
#include <hid-parser/usages.h>
#include <hid/acer12.h>
#include <hid/ambient-light.h>
#include <hid/boot.h>
#include <hid/egalax.h>
#include <hid/eyoyo.h>
#include <hid/ft3x27.h>
#include <hid/hid.h>
#include <hid/paradise.h>
#include <hid/samsung.h>
#include <hid/usages.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fxl/arraysize.h>
#include <lib/fxl/logging.h>
#include <lib/fxl/time/time_point.h>
#include <lib/ui/input/cpp/formatting.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <trace/event.h>
#include <zircon/device/device.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include "garnet/bin/ui/input_reader/fdio_hid_decoder.h"
#include "garnet/bin/ui/input_reader/protocols.h"
namespace {
// Variable to quickly re-enable the hardcoded touchpad reports.
// TODO(ZX-3219): Remove this once touchpads are stable
bool USE_TOUCHPAD_HARDCODED_REPORTS = false;
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;
}
// TODO(SCN-473): Extract sensor IDs from HID.
const size_t kParadiseAccLid = 0;
const size_t kParadiseAccBase = 1;
const size_t kAmbientLight = 2;
} // namespace
namespace mozart {
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;
auto protocol = protocol_;
if (protocol == Protocol::Keyboard) {
FXL_VLOG(2) << "Device " << name() << " has keyboard";
has_keyboard_ = true;
keyboard_descriptor_ = fuchsia::ui::input::KeyboardDescriptor::New();
keyboard_descriptor_->keys.resize(HID_USAGE_KEY_RIGHT_GUI -
HID_USAGE_KEY_A + 1);
for (size_t index = HID_USAGE_KEY_A; index <= HID_USAGE_KEY_RIGHT_GUI;
++index) {
keyboard_descriptor_->keys.at(index - HID_USAGE_KEY_A) = index;
}
keyboard_report_ = fuchsia::ui::input::InputReport::New();
keyboard_report_->keyboard = fuchsia::ui::input::KeyboardReport::New();
} else if (protocol == Protocol::Buttons) {
FXL_VLOG(2) << "Device " << name() << " has buttons";
has_buttons_ = true;
buttons_descriptor_ = fuchsia::ui::input::ButtonsDescriptor::New();
buttons_descriptor_->buttons |= fuchsia::ui::input::kVolumeUp;
buttons_descriptor_->buttons |= fuchsia::ui::input::kVolumeDown;
buttons_descriptor_->buttons |= fuchsia::ui::input::kMicMute;
buttons_report_ = fuchsia::ui::input::InputReport::New();
buttons_report_->buttons = fuchsia::ui::input::ButtonsReport::New();
} else if (protocol == Protocol::Mouse) {
FXL_VLOG(2) << "Device " << name() << " has mouse";
has_mouse_ = true;
mouse_device_type_ = MouseDeviceType::HID;
mouse_descriptor_ = fuchsia::ui::input::MouseDescriptor::New();
// At the moment all mice send relative units, so these min and max values
// do not affect anything. Set them to maximum range.
mouse_descriptor_->rel_x.range.min = INT32_MIN;
mouse_descriptor_->rel_x.range.max = INT32_MAX;
mouse_descriptor_->rel_x.resolution = 1;
mouse_descriptor_->rel_y.range.min = INT32_MIN;
mouse_descriptor_->rel_y.range.max = INT32_MAX;
mouse_descriptor_->rel_y.resolution = 1;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonPrimary;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonSecondary;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonTertiary;
mouse_report_ = fuchsia::ui::input::InputReport::New();
mouse_report_->mouse = fuchsia::ui::input::MouseReport::New();
} else if (protocol == Protocol::BootMouse || protocol == Protocol::Gamepad) {
FXL_VLOG(2) << "Device " << name() << " has mouse";
has_mouse_ = true;
mouse_device_type_ = (protocol == Protocol::BootMouse)
? MouseDeviceType::BOOT
: MouseDeviceType::GAMEPAD;
mouse_descriptor_ = fuchsia::ui::input::MouseDescriptor::New();
mouse_descriptor_->rel_x.range.min = INT32_MIN;
mouse_descriptor_->rel_x.range.max = INT32_MAX;
mouse_descriptor_->rel_x.resolution = 1;
mouse_descriptor_->rel_y.range.min = INT32_MIN;
mouse_descriptor_->rel_y.range.max = INT32_MAX;
mouse_descriptor_->rel_y.resolution = 1;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonPrimary;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonSecondary;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonTertiary;
mouse_report_ = fuchsia::ui::input::InputReport::New();
mouse_report_->mouse = fuchsia::ui::input::MouseReport::New();
} else if (protocol == Protocol::Touch) {
FXL_VLOG(2) << "Device " << name() << " has hid touch";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
Touchscreen::Descriptor touch_desc;
SetDescriptor(&touch_desc);
touchscreen_descriptor_->x.range.min = touch_desc.x_min;
touchscreen_descriptor_->x.range.max = touch_desc.x_max;
touchscreen_descriptor_->x.resolution = touch_desc.x_resolution;
touchscreen_descriptor_->y.range.min = touch_desc.y_min;
touchscreen_descriptor_->y.range.max = touch_desc.y_max;
touchscreen_descriptor_->y.resolution = touch_desc.x_resolution;
touchscreen_descriptor_->max_finger_id = touch_desc.max_finger_id;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::HID;
} else if (protocol == Protocol::Touchpad) {
FXL_VLOG(2) << "Device " << name() << " has hid touchpad";
has_mouse_ = true;
mouse_descriptor_ = fuchsia::ui::input::MouseDescriptor::New();
mouse_device_type_ = MouseDeviceType::TOUCH;
mouse_descriptor_->rel_x.range.min = INT32_MIN;
mouse_descriptor_->rel_x.range.max = INT32_MAX;
mouse_descriptor_->rel_x.resolution = 1;
mouse_descriptor_->rel_y.range.min = INT32_MIN;
mouse_descriptor_->rel_y.range.max = INT32_MAX;
mouse_descriptor_->rel_y.resolution = 1;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonPrimary;
mouse_report_ = fuchsia::ui::input::InputReport::New();
mouse_report_->mouse = fuchsia::ui::input::MouseReport::New();
} else if (protocol == Protocol::Acer12Touch) {
FXL_VLOG(2) << "Device " << name() << " has stylus";
has_stylus_ = true;
stylus_descriptor_ = fuchsia::ui::input::StylusDescriptor::New();
stylus_descriptor_->x.range.min = 0;
stylus_descriptor_->x.range.max = ACER12_STYLUS_X_MAX;
stylus_descriptor_->x.resolution = 1;
stylus_descriptor_->y.range.min = 0;
stylus_descriptor_->y.range.max = ACER12_STYLUS_Y_MAX;
stylus_descriptor_->y.resolution = 1;
stylus_descriptor_->is_invertible = false;
stylus_descriptor_->buttons |= fuchsia::ui::input::kStylusBarrel;
stylus_report_ = fuchsia::ui::input::InputReport::New();
stylus_report_->stylus = fuchsia::ui::input::StylusReport::New();
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = ACER12_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = ACER12_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(jpoichet) do not hardcode this
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::ACER12;
} else if (protocol == Protocol::SamsungTouch) {
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = SAMSUNG_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = SAMSUNG_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(jpoichet) do not hardcode this
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::SAMSUNG;
} else if (protocol == Protocol::ParadiseV1Touch) {
// TODO(cpu): Add support for stylus.
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = PARADISE_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = PARADISE_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(cpu) do not hardcode |max_finger_id|.
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::PARADISEv1;
} else if (protocol == Protocol::ParadiseV2Touch) {
FXL_VLOG(2) << "Device " << name() << " has stylus";
has_stylus_ = true;
stylus_descriptor_ = fuchsia::ui::input::StylusDescriptor::New();
stylus_descriptor_->x.range.min = 0;
stylus_descriptor_->x.range.max = PARADISE_STYLUS_X_MAX;
stylus_descriptor_->x.resolution = 1;
stylus_descriptor_->y.range.min = 0;
stylus_descriptor_->y.range.max = PARADISE_STYLUS_Y_MAX;
stylus_descriptor_->y.resolution = 1;
stylus_descriptor_->is_invertible = false;
stylus_descriptor_->buttons |= fuchsia::ui::input::kStylusBarrel;
stylus_report_ = fuchsia::ui::input::InputReport::New();
stylus_report_->stylus = fuchsia::ui::input::StylusReport::New();
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = PARADISE_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = PARADISE_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(cpu) do not hardcode |max_finger_id|.
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::PARADISEv2;
} else if (protocol == Protocol::ParadiseV3Touch) {
FXL_VLOG(2) << "Device " << name() << " has stylus";
has_stylus_ = true;
stylus_descriptor_ = fuchsia::ui::input::StylusDescriptor::New();
stylus_descriptor_->x.range.min = 0;
stylus_descriptor_->x.range.max = PARADISE_STYLUS_X_MAX;
stylus_descriptor_->x.resolution = 1;
stylus_descriptor_->y.range.min = 0;
stylus_descriptor_->y.range.max = PARADISE_STYLUS_Y_MAX;
stylus_descriptor_->y.resolution = 1;
stylus_descriptor_->is_invertible = false;
stylus_descriptor_->buttons |= fuchsia::ui::input::kStylusBarrel;
stylus_report_ = fuchsia::ui::input::InputReport::New();
stylus_report_->stylus = fuchsia::ui::input::StylusReport::New();
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = PARADISE_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = PARADISE_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(cpu) do not hardcode |max_finger_id|.
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::PARADISEv3;
} else if (protocol == Protocol::ParadiseV1TouchPad) {
FXL_VLOG(2) << "Device " << name() << " has touchpad";
has_mouse_ = true;
mouse_device_type_ = MouseDeviceType::PARADISEv1;
mouse_descriptor_ = fuchsia::ui::input::MouseDescriptor::New();
mouse_descriptor_->rel_x.range.min = INT32_MIN;
mouse_descriptor_->rel_x.range.max = INT32_MAX;
mouse_descriptor_->rel_x.resolution = 1;
mouse_descriptor_->rel_y.range.min = INT32_MIN;
mouse_descriptor_->rel_y.range.max = INT32_MAX;
mouse_descriptor_->rel_y.resolution = 1;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonPrimary;
mouse_report_ = fuchsia::ui::input::InputReport::New();
mouse_report_->mouse = fuchsia::ui::input::MouseReport::New();
} else if (protocol == Protocol::ParadiseV2TouchPad) {
FXL_VLOG(2) << "Device " << name() << " has touchpad";
has_mouse_ = true;
mouse_device_type_ = MouseDeviceType::PARADISEv2;
mouse_descriptor_ = fuchsia::ui::input::MouseDescriptor::New();
mouse_descriptor_->rel_x.range.min = INT32_MIN;
mouse_descriptor_->rel_x.range.max = INT32_MAX;
mouse_descriptor_->rel_x.resolution = 1;
mouse_descriptor_->rel_y.range.min = INT32_MIN;
mouse_descriptor_->rel_y.range.max = INT32_MAX;
mouse_descriptor_->rel_y.resolution = 1;
mouse_descriptor_->buttons |= fuchsia::ui::input::kMouseButtonPrimary;
mouse_report_ = fuchsia::ui::input::InputReport::New();
mouse_report_->mouse = fuchsia::ui::input::MouseReport::New();
} else if (protocol == Protocol::EgalaxTouch) {
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = EGALAX_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = EGALAX_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
touchscreen_descriptor_->max_finger_id = 1;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::EGALAX;
} else if (protocol == Protocol::ParadiseSensor) {
FXL_VLOG(2) << "Device " << name() << " has motion sensors";
sensor_device_type_ = SensorDeviceType::PARADISE;
has_sensors_ = true;
fuchsia::ui::input::SensorDescriptorPtr acc_base =
fuchsia::ui::input::SensorDescriptor::New();
acc_base->type = fuchsia::ui::input::SensorType::ACCELEROMETER;
acc_base->loc = fuchsia::ui::input::SensorLocation::BASE;
sensor_descriptors_[kParadiseAccBase] = std::move(acc_base);
fuchsia::ui::input::SensorDescriptorPtr acc_lid =
fuchsia::ui::input::SensorDescriptor::New();
acc_lid->type = fuchsia::ui::input::SensorType::ACCELEROMETER;
acc_lid->loc = fuchsia::ui::input::SensorLocation::LID;
sensor_descriptors_[kParadiseAccLid] = std::move(acc_lid);
sensor_report_ = fuchsia::ui::input::InputReport::New();
sensor_report_->sensor = fuchsia::ui::input::SensorReport::New();
} else if (protocol == Protocol::EyoyoTouch) {
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = EYOYO_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = EYOYO_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(jpoichet) do not hardcode this
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::EYOYO;
} else if (protocol == Protocol::LightSensor) {
FXL_VLOG(2) << "Device " << name() << " has an ambient light sensor";
sensor_device_type_ = SensorDeviceType::AMBIENT_LIGHT;
has_sensors_ = true;
fuchsia::ui::input::SensorDescriptorPtr desc =
fuchsia::ui::input::SensorDescriptor::New();
desc->type = fuchsia::ui::input::SensorType::LIGHTMETER;
desc->loc = fuchsia::ui::input::SensorLocation::UNKNOWN;
sensor_descriptors_[kAmbientLight] = std::move(desc);
sensor_report_ = fuchsia::ui::input::InputReport::New();
sensor_report_->sensor = fuchsia::ui::input::SensorReport::New();
} else if (protocol == Protocol::EyoyoTouch) {
FXL_VLOG(2) << "Device " << name() << " has touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = EYOYO_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = EYOYO_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(jpoichet) do not hardcode this
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::EYOYO;
} else if (protocol == Protocol::Ft3x27Touch) {
FXL_VLOG(2) << "Device " << name() << " has a touchscreen";
has_touchscreen_ = true;
touchscreen_descriptor_ = fuchsia::ui::input::TouchscreenDescriptor::New();
touchscreen_descriptor_->x.range.min = 0;
touchscreen_descriptor_->x.range.max = FT3X27_X_MAX;
touchscreen_descriptor_->x.resolution = 1;
touchscreen_descriptor_->y.range.min = 0;
touchscreen_descriptor_->y.range.max = FT3X27_Y_MAX;
touchscreen_descriptor_->y.resolution = 1;
// TODO(SCN-867) Use HID parsing for all touch devices
// will remove the need for this hardcoding
touchscreen_descriptor_->max_finger_id = 255;
touchscreen_report_ = fuchsia::ui::input::InputReport::New();
touchscreen_report_->touchscreen =
fuchsia::ui::input::TouchscreenReport::New();
touch_device_type_ = TouchDeviceType::FT3X27;
} else {
FXL_VLOG(2) << "Device " << name() << " has unsupported HID device";
return false;
}
event_ = hid_decoder_->GetEvent();
if (!event_)
return false;
NotifyRegistry();
return true;
}
void InputInterpreter::NotifyRegistry() {
if (has_sensors_) {
FXL_DCHECK(kMaxSensorCount == sensor_descriptors_.size());
FXL_DCHECK(kMaxSensorCount == sensor_devices_.size());
for (size_t i = 0; i < kMaxSensorCount; ++i) {
if (sensor_descriptors_[i]) {
fuchsia::ui::input::DeviceDescriptor descriptor;
zx_status_t status =
fidl::Clone(sensor_descriptors_[i], &descriptor.sensor);
FXL_DCHECK(status == ZX_OK)
<< "Sensor descriptor: clone failed (status=" << status << ")";
registry_->RegisterDevice(std::move(descriptor),
sensor_devices_[i].NewRequest());
}
}
// Sensor devices can't be anything else, so don't bother with other types.
return;
}
fuchsia::ui::input::DeviceDescriptor descriptor;
if (has_keyboard_) {
fidl::Clone(keyboard_descriptor_, &descriptor.keyboard);
}
if (has_mouse_) {
fidl::Clone(mouse_descriptor_, &descriptor.mouse);
}
if (has_stylus_) {
fidl::Clone(stylus_descriptor_, &descriptor.stylus);
}
if (has_touchscreen_) {
fidl::Clone(touchscreen_descriptor_, &descriptor.touchscreen);
}
registry_->RegisterDevice(std::move(descriptor), 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;
}
if (has_keyboard_) {
hardcoded_.ParseKeyboardReport(report.data(), rc, keyboard_report_.get());
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
keyboard_report_->trace_id, "device_type", "keyboard");
input_device_->DispatchReport(CloneReport(*keyboard_report_));
}
}
if (has_buttons_) {
if (!hardcoded_.ParseButtonsReport(report.data(), rc,
buttons_report_.get()))
return false;
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
buttons_report_->trace_id, "device_type", "buttons");
input_device_->DispatchReport(CloneReport(*buttons_report_));
}
}
switch (mouse_device_type_) {
case MouseDeviceType::BOOT:
hardcoded_.ParseMouseReport(report.data(), rc, mouse_report_.get());
if (!discard) {
TRACE_ASYNC_BEGIN("inputinput", "dispatch_1_report_to_listener",
mouse_report_->trace_id, "device_type", "mouse");
input_device_->DispatchReport(CloneReport(*mouse_report_));
}
break;
case MouseDeviceType::TOUCH:
if (ParseTouchpadReport(report.data(), rc, mouse_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
mouse_report_->trace_id, "device_type", "touchpad");
input_device_->DispatchReport(CloneReport(*mouse_report_));
}
}
break;
case MouseDeviceType::HID:
if (ParseHidMouseReport(report.data(), rc, mouse_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
mouse_report_->trace_id, "device_type", "mouse");
input_device_->DispatchReport(CloneReport(*mouse_report_));
}
}
break;
case MouseDeviceType::PARADISEv1:
if (hardcoded_.ParseParadiseTouchpadReportV1(report.data(), rc,
mouse_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
mouse_report_->trace_id, "device_type", "touchpad");
input_device_->DispatchReport(CloneReport(*mouse_report_));
}
}
break;
case MouseDeviceType::PARADISEv2:
if (hardcoded_.ParseParadiseTouchpadReportV2(report.data(), rc,
mouse_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
mouse_report_->trace_id, "device_type", "touchpad");
input_device_->DispatchReport(CloneReport(*mouse_report_));
}
}
break;
case MouseDeviceType::GAMEPAD:
// TODO(cpu): remove this once we have a good way to test gamepad.
if (hardcoded_.ParseGamepadMouseReport(report.data(), rc,
mouse_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
mouse_report_->trace_id, "device_type", "gamepad");
input_device_->DispatchReport(CloneReport(*mouse_report_));
}
}
break;
case MouseDeviceType::NONE:
break;
}
switch (touch_device_type_) {
case TouchDeviceType::HID:
if (ParseTouchscreenReport(report.data(), rc,
touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
break;
case TouchDeviceType::ACER12:
if (report[0] == ACER12_RPT_ID_STYLUS) {
if (hardcoded_.ParseAcer12StylusReport(report.data(), rc,
stylus_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
stylus_report_->trace_id, "device_type",
"stylus");
input_device_->DispatchReport(CloneReport(*stylus_report_));
}
}
} else if (report[0] == ACER12_RPT_ID_TOUCH) {
if (hardcoded_.ParseAcer12TouchscreenReport(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
}
break;
case TouchDeviceType::SAMSUNG:
if (report[0] == SAMSUNG_RPT_ID_TOUCH) {
if (hardcoded_.ParseSamsungTouchscreenReport(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
}
break;
case TouchDeviceType::PARADISEv1:
if (report[0] == PARADISE_RPT_ID_TOUCH) {
if (hardcoded_.ParseParadiseTouchscreenReportV1(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
}
break;
case TouchDeviceType::PARADISEv2:
if (report[0] == PARADISE_RPT_ID_TOUCH) {
if (hardcoded_.ParseParadiseTouchscreenReportV2(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
} else if (report[0] == PARADISE_RPT_ID_STYLUS) {
if (hardcoded_.ParseParadiseStylusReport(report.data(), rc,
stylus_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
stylus_report_->trace_id, "device_type",
"stylus");
input_device_->DispatchReport(CloneReport(*stylus_report_));
}
}
}
break;
case TouchDeviceType::PARADISEv3:
if (report[0] == PARADISE_RPT_ID_TOUCH) {
// Paradise V3 uses the same touchscreen report as v1.
if (hardcoded_.ParseParadiseTouchscreenReportV1(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
} else if (report[0] == PARADISE_RPT_ID_STYLUS) {
if (hardcoded_.ParseParadiseStylusReport(report.data(), rc,
stylus_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
stylus_report_->trace_id, "device_type",
"stylus");
input_device_->DispatchReport(CloneReport(*stylus_report_));
}
}
}
break;
case TouchDeviceType::EGALAX:
if (report[0] == EGALAX_RPT_ID_TOUCH) {
if (hardcoded_.ParseEGalaxTouchscreenReport(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
}
break;
case TouchDeviceType::EYOYO:
if (report[0] == EYOYO_RPT_ID_TOUCH) {
if (hardcoded_.ParseEyoyoTouchscreenReport(report.data(), rc,
touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
}
break;
case TouchDeviceType::FT3X27:
if (report[0] == FT3X27_RPT_ID_TOUCH) {
if (hardcoded_.ParseFt3x27TouchscreenReport(
report.data(), rc, touchscreen_report_.get())) {
if (!discard) {
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
touchscreen_report_->trace_id, "device_type",
"touchscreen");
input_device_->DispatchReport(CloneReport(*touchscreen_report_));
}
}
}
break;
default:
break;
}
switch (sensor_device_type_) {
case SensorDeviceType::PARADISE:
if (hardcoded_.ParseParadiseSensorReport(report.data(), rc, &sensor_idx_,
sensor_report_.get())) {
if (!discard) {
FXL_DCHECK(sensor_idx_ < kMaxSensorCount);
FXL_DCHECK(sensor_devices_[sensor_idx_]);
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
sensor_report_->trace_id, "device_type", "sensor");
sensor_devices_[sensor_idx_]->DispatchReport(
CloneReport(*sensor_report_));
}
}
break;
case SensorDeviceType::AMBIENT_LIGHT:
if (hardcoded_.ParseAmbientLightSensorReport(
report.data(), rc, &sensor_idx_, sensor_report_.get())) {
if (!discard) {
FXL_DCHECK(sensor_idx_ < kMaxSensorCount);
FXL_DCHECK(sensor_devices_[sensor_idx_]);
TRACE_ASYNC_BEGIN("input", "dispatch_1_report_to_listener",
sensor_report_->trace_id, "device_type",
"ambient_light");
sensor_devices_[sensor_idx_]->DispatchReport(
CloneReport(*sensor_report_));
}
}
break;
default:
break;
}
return true;
}
bool InputInterpreter::ParseHidMouseReport(
uint8_t* report, size_t len,
fuchsia::ui::input::InputReport* mouse_report) {
Mouse::Report mouse;
if (!ParseReport(report, len, &mouse))
return false;
mouse_report->event_time = InputEventTimestampNow();
mouse_report->trace_id = TRACE_NONCE();
mouse_report->mouse->rel_x = mouse.rel_x;
mouse_report->mouse->rel_y = mouse.rel_y;
mouse_report->mouse->pressed_buttons = 0;
mouse_report->mouse->pressed_buttons |=
mouse.left_click ? fuchsia::ui::input::kMouseButtonPrimary : 0;
return true;
}
// This logic converts the multi-finger report from the touchpad into
// a mouse report. It does this by only tracking the first finger that
// is placed down, and converting the absolution finger position into
// relative X and Y movements. All other fingers besides the tracking
// finger are ignored.
bool InputInterpreter::ParseTouchpadReport(
uint8_t* report, size_t len,
fuchsia::ui::input::InputReport* mouse_report) {
Touchscreen::Report touchpad;
if (!ParseReport(report, len, &touchpad)) {
return false;
}
mouse_report->event_time = InputEventTimestampNow();
mouse_report->trace_id = TRACE_NONCE();
mouse_report->mouse->rel_x = 0;
mouse_report->mouse->rel_y = 0;
mouse_report->mouse->pressed_buttons = 0;
// If all fingers are lifted reset our tracking finger.
if (touchpad.contact_count == 0) {
has_touch_ = false;
tracking_finger_was_lifted_ = true;
return true;
}
// If we don't have a tracking finger then set one.
if (!has_touch_) {
has_touch_ = true;
tracking_finger_was_lifted_ = false;
tracking_finger_id_ = touchpad.contacts[0].id;
mouse_abs_x_ = touchpad.contacts[0].x;
mouse_abs_y_ = touchpad.contacts[0].y;
return true;
}
// Find the finger we are tracking.
Touchscreen::ContactReport* contact = nullptr;
for (size_t i = 0; i < touchpad.contact_count; i++) {
if (touchpad.contacts[i].id == tracking_finger_id_) {
contact = &touchpad.contacts[i];
break;
}
}
// If our tracking finger isn't pressed return early.
if (contact == nullptr) {
tracking_finger_was_lifted_ = true;
return true;
}
// If our tracking finger was lifted then reset the abs values otherwise
// the pointer will jump rapidly.
if (tracking_finger_was_lifted_) {
tracking_finger_was_lifted_ = false;
mouse_abs_x_ = contact->x;
mouse_abs_y_ = contact->y;
}
// The touch driver returns in units of 10^-5m, but the resolution expected
// by |mouse_report_| is 10^-3.
mouse_report->mouse->rel_x = (contact->x - mouse_abs_x_) / 100;
mouse_report->mouse->rel_y = (contact->y - mouse_abs_y_) / 100;
mouse_report->mouse->pressed_buttons =
touchpad.button ? fuchsia::ui::input::kMouseButtonPrimary : 0;
mouse_abs_x_ = touchpad.contacts[0].x;
mouse_abs_y_ = touchpad.contacts[0].y;
return true;
}
bool InputInterpreter::ParseTouchscreenReport(
uint8_t* report, size_t len,
fuchsia::ui::input::InputReport* touchscreen_report) {
Touchscreen::Report touchscreen;
if (!ParseReport(report, len, &touchscreen)) {
return false;
}
touchscreen_report->event_time = InputEventTimestampNow();
touchscreen_report->trace_id = TRACE_NONCE();
touchscreen_report->touchscreen->touches.resize(touchscreen.contact_count);
for (size_t i = 0; i < touchscreen.contact_count; ++i) {
fuchsia::ui::input::Touch touch;
touch.finger_id = touchscreen.contacts[i].id;
touch.x = touchscreen.contacts[i].x;
touch.y = touchscreen.contacts[i].y;
// TODO(SCN-1188): Add support for contact ellipse.
touch.width = 5;
touch.height = 5;
touchscreen_report->touchscreen->touches.at(i) = std::move(touch);
}
return true;
}
Protocol 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::kSensor),
static_cast<uint32_t>(Sensor::kAmbientLight)},
Protocol::LightSensor},
{{static_cast<uint16_t>(Page::kConsumer),
static_cast<uint32_t>(Consumer::kConsumerControl)},
Protocol::Buttons},
{{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::kGenericDesktop),
static_cast<uint32_t>(GenericDesktop::kMouse)},
Protocol::Mouse},
// Add more sensors here
};
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::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;
}
// For the rest of devices (fuchsia_hardware_input_BootProtocol_NONE) we need
// to parse the report descriptor. The legacy method involves memcmp() of
// known descriptors which cover the next 8 devices:
int desc_size;
auto desc = hid_decoder_->ReadReportDescriptor(&desc_size);
if (desc_size == 0) {
return false;
}
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())) {
hid_decoder_->SetupDevice(HidDecoder::Device::SAMSUNG);
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_touch_v3_report_desc(desc.data(), desc.size())) {
protocol_ = Protocol::ParadiseV3Touch;
return true;
}
if (USE_TOUCHPAD_HARDCODED_REPORTS) {
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;
}
if (is_eyoyo_touch_report_desc(desc.data(), desc.size())) {
hid_decoder_->SetupDevice(HidDecoder::Device::EYOYO);
protocol_ = Protocol::EyoyoTouch;
return true;
}
// TODO(SCN-867) Use HID parsing for all touch devices
// will remove the need for this
if (is_ft3x27_touch_report_desc(desc.data(), desc.size())) {
hid_decoder_->SetupDevice(HidDecoder::Device::FT3X27);
protocol_ = Protocol::Ft3x27Touch;
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::ReportDescriptor* input_desc = nullptr;
for (size_t rep = 0; rep < count; rep++) {
const hid::ReportDescriptor* desc = &dev_desc->report[rep];
if (desc->input_count != 0) {
input_desc = desc;
break;
}
}
if (input_desc == nullptr) {
FXL_LOG(ERROR) << "no input report fields for " << name();
return false;
}
// Traverse up the nested collections to the Application collection.
auto 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(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;
// 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;
} else {
protocol_ = ExtractProtocol(collection->usage);
switch (protocol_) {
case Protocol::LightSensor:
hardcoded_.ParseAmbientLightDescriptor(input_desc->input_fields,
input_desc->input_count);
break;
case Protocol::Buttons:
hardcoded_.ParseButtonsDescriptor(input_desc->input_fields,
input_desc->input_count);
break;
case Protocol::Touchpad:
// Fallthrough
case Protocol::Touch: {
bool success = ts_.ParseTouchscreenDescriptor(input_desc);
if (!success) {
FXL_LOG(ERROR) << "invalid touchscreen descriptor for " << name();
return false;
}
break;
}
case Protocol::Mouse: {
bool success = mouse_.ParseDescriptor(input_desc);
if (!success) {
FXL_LOG(ERROR) << "invalid mouse descriptor for " << name();
return false;
}
break;
}
// Add more protocols here
default:
return false;
}
}
return true;
}
bool InputInterpreter::ParseReport(const uint8_t* report, size_t len,
Touchscreen::Report* touchscreen) {
if (report[0] != ts_.report_id()) {
FXL_VLOG(0) << name() << " Touchscreen report "
<< static_cast<uint32_t>(report[0])
<< " does not match report id "
<< static_cast<uint32_t>(ts_.report_id());
return false;
}
return ts_.ParseReport(report, len, touchscreen);
}
bool InputInterpreter::ParseReport(const uint8_t* report, size_t len,
Mouse::Report* mouse) {
if (report[0] != mouse_.report_id()) {
FXL_VLOG(0) << name() << " Mouse report "
<< static_cast<uint32_t>(report[0])
<< " does not match report id "
<< static_cast<uint32_t>(mouse_.report_id());
return false;
}
return mouse_.ParseReport(report, len, mouse);
}
bool InputInterpreter::SetDescriptor(Touchscreen::Descriptor* touch_desc) {
return ts_.SetDescriptor(touch_desc);
}
} // namespace mozart