[input_sample] Sample Input Driver Using input_reports_reader
Sample Input Driver implementing fuchsia.input.report which uses
input_reports_reader to send fake mouse reports every 1 second.
Test: tested on QEMU that we can use `ffx driver print-input-report
read` to read the fake reports from sample driver.
bazel run --config=fuchsia_x64 src/input_sample:pkg.component
tools/ffx driver print-input-report descriptor
tools/ffx driver print-input-report read
Bug: 103070
Change-Id: I3eeb571f044e4570aa93c16607e5def0a30134ef
Reviewed-on: https://fuchsia-review.googlesource.com/c/sdk-samples/drivers/+/692855
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
Commit-Queue: Ruby Zhuang <rdzhuang@google.com>
Reviewed-by: David Gilhooley <dgilhooley@google.com>
diff --git a/src/input_sample/BUILD.bazel b/src/input_sample/BUILD.bazel
new file mode 100644
index 0000000..36436f3
--- /dev/null
+++ b/src/input_sample/BUILD.bazel
@@ -0,0 +1,57 @@
+load(
+ "@rules_fuchsia//fuchsia:defs.bzl",
+ "fuchsia_component_manifest",
+ "fuchsia_driver_bytecode_bind_rules",
+ "fuchsia_driver_component",
+ "fuchsia_package",
+)
+
+fuchsia_driver_bytecode_bind_rules(
+ name = "bind_bytecode",
+ output = "input_sample.bindbc",
+ rules = "input_sample.bind",
+ deps = [ "@fuchsia_sdk//bind/fuchsia.acpi" ],
+)
+
+cc_binary(
+ name = "input_sample",
+ srcs = [
+ "input_sample.cc",
+ "input_sample.h",
+ ],
+ linkshared = True,
+ deps = [
+ "@fuchsia_sdk//fidl/fuchsia.device.fs:fuchsia.device.fs_llcpp_cc",
+ "@fuchsia_sdk//fidl/fuchsia.driver.compat:fuchsia.driver.compat_llcpp_cc",
+ "@fuchsia_sdk//fidl/fuchsia.input.report:fuchsia.input.report_llcpp_cc",
+ "@fuchsia_sdk//fidl/zx:zx_cc",
+ "@fuchsia_sdk//pkg/async-cpp",
+ "@fuchsia_sdk//pkg/driver2-llcpp",
+ "@fuchsia_sdk//pkg/driver_runtime_cpp",
+ "@fuchsia_sdk//pkg/fidl-llcpp",
+ "@fuchsia_sdk//pkg/input_report_reader",
+ "@fuchsia_sdk//pkg/sys_component_llcpp",
+ "@fuchsia_sdk//pkg/zx",
+ ],
+)
+
+fuchsia_component_manifest(
+ name = "manifest",
+ src = "meta/input_sample.cml",
+)
+
+fuchsia_driver_component(
+ name = "component",
+ bind_bytecode = ":bind_bytecode",
+ driver_lib = ":input_sample",
+ manifest = ":manifest",
+)
+
+fuchsia_package(
+ name = "pkg",
+ package_name = "input_sample",
+ visibility = ["//visibility:public"],
+ deps = [
+ ":component",
+ ],
+)
diff --git a/src/input_sample/README.md b/src/input_sample/README.md
new file mode 100644
index 0000000..4ad8d5d
--- /dev/null
+++ b/src/input_sample/README.md
@@ -0,0 +1,107 @@
+# Input Driver Sample using Fuchsia SDK
+
+This sample input driver implements fuchsia.input.report using the input_reports_reader SDK to send fake mouse reports every 1 second.
+
+## Run and test driver
+
+After starting up the emulator
+
+1. Load the sample driver
+
+ ```
+ bazel run --config=fuchsia_x64 src/input_sample:pkg.component
+ ```
+
+2. Read the fake input mouse descriptor
+
+ ```
+ tools/ffx driver print-input-report descriptor
+ ```
+
+ This should return a list of descriptors. The fake mouse descriptor for this driver is
+
+ ```
+ Descriptor from file: "class/input-report/002"
+ {
+ device_info: {
+ vendor_id: 305419896
+ product_id: 2271560481
+ version: 0
+ }
+ mouse: {
+ input: {
+ movement_x: {
+ range: {
+ min: -127
+ max: 127
+ }
+ unit: {
+ type: None
+ exponent: 0
+ }
+ }
+ movement_y: {
+ range: {
+ min: -127
+ max: 127
+ }
+ unit: {
+ type: None
+ exponent: 0
+ }
+ }
+ scroll_v: None
+ scroll_h: None
+ buttons: [1, 2, 3]
+ position_x: None
+ position_y: None
+ }
+ }
+ sensor: None
+ touch: None
+ keyboard: None
+ consumer_control: None
+ }
+ ```
+
+ In particular, it should be the one with
+
+ ```
+ vendor_id: 305419896 // 0x12345678
+ product_id: 2271560481 // 0x87654321
+ ```
+
+ You may exit this with CTRL-C.
+
+2. Read the fake input mouse reports
+
+ ```
+ tools/ffx driver print-input-report read
+ ```
+
+ This will produce logs that look like
+
+ ```
+ {
+ event_time: 335266867991
+ trace_id: 9
+ report_id: None
+ mouse: {
+ movement_x: 29
+ movement_y: 29
+ scroll_v: None
+ scroll_h: None
+ pressed_buttons: [1, 3]
+ position_x: None
+ position_y: None
+ }
+ sensor: None
+ touch: None
+ keyboard: None
+ consumer_control: None
+ }
+ ```
+
+ once a second. Each second movement_x and movement_y should increment, pressed_buttons should change, and event_time should also change.
+
+ You may exit this with CTRL-C.
diff --git a/src/input_sample/input_sample.bind b/src/input_sample/input_sample.bind
new file mode 100644
index 0000000..423c152
--- /dev/null
+++ b/src/input_sample/input_sample.bind
@@ -0,0 +1,14 @@
+// Copyright 2022 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.
+
+using fuchsia.acpi;
+
+// Bind to an unused ACPI node in Qemu or Atlas.
+// TODO(fxb/100716): Bind to a test node once it's available.
+if fuchsia.acpi.hid == "QEMU0002" {
+ true;
+} else {
+ fuchsia.acpi.hid == "PNP0C02";
+}
+
diff --git a/src/input_sample/input_sample.cc b/src/input_sample/input_sample.cc
new file mode 100644
index 0000000..de2f5df
--- /dev/null
+++ b/src/input_sample/input_sample.cc
@@ -0,0 +1,213 @@
+// Copyright 2022 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 "src/input_sample/input_sample.h"
+
+#include <fidl/fuchsia.component.decl/cpp/wire.h>
+#include <fidl/fuchsia.driver.compat/cpp/wire.h>
+#include <lib/async/cpp/task.h>
+#include <lib/driver2/record_cpp.h>
+
+#include <unistd.h>
+
+namespace fdf {
+using namespace fuchsia_driver_framework;
+} // namespace fdf
+
+namespace input_sample {
+
+namespace {
+
+namespace fcd = fuchsia_component_decl;
+namespace fio = fuchsia_io;
+
+zx::status<fidl::ClientEnd<fuchsia_driver_compat::Device>> ConnectToParentDevice(
+ const driver::Namespace* ns, std::string_view name) {
+ auto result = ns->OpenService<fuchsia_driver_compat::Service>(name);
+ if (result.is_error()) {
+ return result.take_error();
+ }
+ return result.value().connect_device();
+}
+
+constexpr uint8_t kMouseButtonCount = 3;
+
+} // namespace
+
+void SampleInputReport::ToFidlInputReport(
+ fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report,
+ fidl::AnyArena& arena) {
+ std::vector<uint8_t> pressed_buttons;
+ for (uint8_t i = 0; i < kMouseButtonCount; i++) {
+ if (buttons & (1 << i)) {
+ pressed_buttons.push_back(i + 1);
+ }
+ }
+ fidl::VectorView<uint8_t> buttons_vec(arena, pressed_buttons.size());
+ size_t idx = 0;
+ for (const auto& button : pressed_buttons) {
+ buttons_vec[idx++] = button;
+ }
+
+ auto mouse_input_rpt = fuchsia_input_report::wire::MouseInputReport::Builder(arena);
+ mouse_input_rpt.pressed_buttons(buttons_vec);
+ mouse_input_rpt.movement_x(rel_x);
+ mouse_input_rpt.movement_y(rel_y);
+
+ input_report.mouse(mouse_input_rpt.Build());
+ input_report.event_time(event_time.get());
+}
+
+void InputSampleDriver::SendFakeReport() {
+ // Update fake mouse report
+ report_.event_time = zx::time(zx_clock_get_monotonic());
+ report_.buttons++;
+ report_.rel_x++;
+ report_.rel_y++;
+
+ // Send fake mouse report
+ input_report_readers_.SendReportToAllReaders(report_);
+
+ // Run again in 1 second
+ async::PostDelayedTask(
+ dispatcher_,
+ [this]() mutable {
+ SendFakeReport();
+ },
+ zx::sec(1));
+}
+
+// static
+zx::status<std::unique_ptr<InputSampleDriver>> InputSampleDriver::Start(
+ fdf::wire::DriverStartArgs& start_args, fdf::UnownedDispatcher dispatcher,
+ fidl::WireSharedClient<fdf::Node> node, driver::Namespace ns, driver::Logger logger) {
+ auto driver = std::make_unique<InputSampleDriver>(dispatcher->async_dispatcher(), std::move(node),
+ std::move(ns), std::move(logger));
+ auto result = driver->Run(std::move(start_args.outgoing_dir()));
+ if (result.is_error()) {
+ return result.take_error();
+ }
+ return zx::ok(std::move(driver));
+}
+
+zx::status<> InputSampleDriver::Run(fidl::ServerEnd<fio::Directory> outgoing_dir) {
+ // Connect to DevfsExporter.
+ auto exporter = ns_.Connect<fuchsia_device_fs::Exporter>();
+ if (exporter.status_value() != ZX_OK) {
+ return exporter.take_error();
+ }
+ exporter_ = fidl::WireClient(std::move(*exporter), dispatcher_);
+
+ // Connect to parent.
+ auto parent = ConnectToParentDevice(&ns_, "default");
+ if (parent.status_value() != ZX_OK) {
+ FDF_SLOG(ERROR, "Failed to connect to parent", KV("status", parent.status_string()));
+ return parent.take_error();
+ }
+
+ auto result = fidl::WireCall(*parent)->GetTopologicalPath();
+ if (!result.ok()) {
+ return zx::error(result.status());
+ }
+
+ std::string path(result->path.data(), result->path.size());
+
+ // TODO (fxbug.dev/103199): migrate off this when fuchsia.input.report InputDevice is discoverable
+ auto status = outgoing_.AddProtocol<fuchsia_input_report::InputDevice>(this, Name());
+ if (status.status_value() != ZX_OK) {
+ return status;
+ }
+
+ path.append("/");
+ path.append(Name());
+
+ FDF_LOG(INFO, "Exporting device to: %s", path.data());
+
+ // Serve a connection to outgoing.
+ auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
+ if (endpoints.is_error()) {
+ return endpoints.take_error();
+ }
+ {
+ auto status = outgoing_.Serve(std::move(endpoints->server));
+ if (status.is_error()) {
+ return status.take_error();
+ }
+ }
+
+ // Export our protocol.
+ exporter_
+ ->Export(std::move(endpoints->client),
+ // TODO (fxbug.dev/103199): migrate off this when fuchsia.input.report
+ // InputDevice is discoverable
+ fidl::StringView::FromExternal(std::string("svc/").append(Name())),
+ fidl::StringView::FromExternal(path),
+ /* INPUT_REPORT */ 26) // TODO (fxbug.dev/98831): Non-hardcoded proto_id
+ .Then([this](fidl::WireUnownedResult<fuchsia_device_fs::Exporter::Export>& result) {
+ if (!result.ok()) {
+ FDF_LOG(ERROR, "Exporting failed with: %s", result.status_string());
+ }
+ });
+
+ status = outgoing_.Serve(std::move(outgoing_dir));
+ if (status.is_error()) {
+ FDF_LOG(ERROR, "Could not serve: %d", status.error_value());
+ return status;
+ }
+
+ // Start sending fake reports
+ SendFakeReport();
+
+ return zx::ok();
+}
+
+void InputSampleDriver::GetInputReportsReader(GetInputReportsReaderRequestView request,
+ GetInputReportsReaderCompleter::Sync& completer) {
+ zx_status_t status = input_report_readers_.CreateReader(dispatcher_, std::move(request->reader));
+ if (status != ZX_OK) {
+ FDF_LOG(ERROR, "Could not create input report reader: %d", status);
+ }
+}
+
+void InputSampleDriver::GetDescriptor(GetDescriptorRequestView request,
+ GetDescriptorCompleter::Sync& completer) {
+ fidl::Arena allocator;
+ auto descriptor = fuchsia_input_report::wire::DeviceDescriptor::Builder(allocator);
+
+ fuchsia_input_report::wire::DeviceInfo device_info;
+ // We pick random VID/PIDs here, but these should be taken from the HID device,
+ // or defined in fuchsia.input.report/device_ids.fidl
+ device_info.vendor_id = 0x12345678;
+ device_info.product_id = 0x87654321;
+
+ fidl::VectorView<uint8_t> buttons(allocator, kMouseButtonCount);
+ buttons[0] = 0x01;
+ buttons[1] = 0x02;
+ buttons[2] = 0x03;
+
+ constexpr fuchsia_input_report::wire::Axis movement_x{
+ .range = {.min = -127, .max = 127},
+ .unit = {.type = fuchsia_input_report::wire::UnitType::kNone, .exponent = 0},
+ };
+ constexpr fuchsia_input_report::wire::Axis movement_y{
+ .range = {.min = -127, .max = 127},
+ .unit = {.type = fuchsia_input_report::wire::UnitType::kNone, .exponent = 0},
+ };
+
+ auto mouse_in_desc = fuchsia_input_report::wire::MouseInputDescriptor::Builder(allocator);
+ mouse_in_desc.buttons(buttons);
+ mouse_in_desc.movement_x(movement_x);
+ mouse_in_desc.movement_y(movement_y);
+
+ auto mouse_descriptor = fuchsia_input_report::wire::MouseDescriptor::Builder(allocator);
+ mouse_descriptor.input(mouse_in_desc.Build());
+ descriptor.mouse(mouse_descriptor.Build());
+ descriptor.device_info(device_info);
+
+ completer.Reply(descriptor.Build());
+}
+
+} // namespace input_sample
+
+FUCHSIA_DRIVER_RECORD_CPP_V1(input_sample::InputSampleDriver);
diff --git a/src/input_sample/input_sample.h b/src/input_sample/input_sample.h
new file mode 100644
index 0000000..9870eb7
--- /dev/null
+++ b/src/input_sample/input_sample.h
@@ -0,0 +1,94 @@
+// Copyright 2022 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.
+
+#ifndef FUCHSIA_SDK_EXAMPLES_CC_INPUT_SAMPLE_H_
+#define FUCHSIA_SDK_EXAMPLES_CC_INPUT_SAMPLE_H_
+
+#include <fidl/fuchsia.device.fs/cpp/wire.h>
+#include <fidl/fuchsia.driver.framework/cpp/wire.h>
+#include <fidl/fuchsia.input.report/cpp/wire.h>
+#include <lib/async/dispatcher.h>
+#include <lib/driver2/namespace.h>
+#include <lib/driver2/structured_logger.h>
+#include <lib/fdf/cpp/dispatcher.h>
+#include <lib/input_report_reader/reader.h>
+#include <lib/sys/component/llcpp/outgoing_directory.h>
+#include <lib/zx/status.h>
+
+#include <memory>
+#include <mutex>
+
+namespace input_sample {
+
+struct SampleInputReport {
+ zx::time event_time;
+ uint8_t buttons = 0;
+ int8_t rel_x = 0;
+ int8_t rel_y = 0;
+
+ void ToFidlInputReport(
+ fidl::WireTableBuilder<::fuchsia_input_report::wire::InputReport>& input_report,
+ fidl::AnyArena& arena);
+};
+
+
+class InputSampleDriver : public fidl::WireServer<fuchsia_input_report::InputDevice> {
+ public:
+ InputSampleDriver(async_dispatcher_t* dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+ driver::Logger logger)
+ : dispatcher_(dispatcher),
+ outgoing_(component::OutgoingDirectory::Create(dispatcher)),
+ node_(std::move(node)),
+ ns_(std::move(ns)),
+ logger_(std::move(logger)) {}
+ virtual ~InputSampleDriver() = default;
+
+ static constexpr const char* Name() { return "input-sample"; }
+
+ static zx::status<std::unique_ptr<InputSampleDriver>> Start(
+ fuchsia_driver_framework::wire::DriverStartArgs& start_args,
+ fdf::UnownedDispatcher dispatcher,
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
+ driver::Logger logger);
+
+ void GetInputReportsReader(GetInputReportsReaderRequestView request,
+ GetInputReportsReaderCompleter::Sync& completer) override;
+ void GetDescriptor(GetDescriptorRequestView request,
+ GetDescriptorCompleter::Sync& completer) override;
+ void SendOutputReport(SendOutputReportRequestView request,
+ SendOutputReportCompleter::Sync& completer) override {
+ completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
+ }
+ void GetFeatureReport(GetFeatureReportRequestView request,
+ GetFeatureReportCompleter::Sync& completer) override {
+ completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
+ }
+ void SetFeatureReport(SetFeatureReportRequestView request,
+ SetFeatureReportCompleter::Sync& completer) override {
+ completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
+ }
+ void GetInputReport(GetInputReportRequestView request,
+ GetInputReportCompleter::Sync& completer) override {
+ completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
+ }
+
+ private:
+ zx::status<> Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir);
+ void SendFakeReport();
+
+ async_dispatcher_t* const dispatcher_;
+ component::OutgoingDirectory outgoing_;
+ fidl::WireSharedClient<fuchsia_driver_framework::Node> node_;
+ driver::Namespace ns_;
+ driver::Logger logger_;
+ fidl::WireClient<fuchsia_device_fs::Exporter> exporter_;
+
+ input_report_reader::InputReportReaderManager<SampleInputReport> input_report_readers_;
+ SampleInputReport report_;
+};
+
+} // namespace input_sample
+
+#endif // FUCHSIA_SDK_EXAMPLES_CC_INPUT_SAMPLE_H_
diff --git a/src/input_sample/meta/input_sample.cml b/src/input_sample/meta/input_sample.cml
new file mode 100644
index 0000000..b9061d0
--- /dev/null
+++ b/src/input_sample/meta/input_sample.cml
@@ -0,0 +1,29 @@
+{
+ program: {
+ runner: 'driver',
+ binary: 'lib/libinput_sample.so',
+ bind: 'meta/bind/input_sample.bindbc'
+ },
+ use: [
+ {
+ protocol: [
+ 'fuchsia.logger.LogSink',
+ 'fuchsia.device.fs.Exporter'
+ ],
+ },
+ {
+ directory: 'fuchsia.driver.compat.Service-default',
+ rights: ['rw*'],
+ path: '/fuchsia.driver.compat.Service/default',
+ }
+ ],
+ capabilities: [
+ { protocol: 'fuchsia.input.report.InputDevice' },
+ ],
+ expose: [
+ {
+ protocol: 'fuchsia.input.report.InputDevice',
+ from: 'self',
+ },
+ ],
+}