Revert "[input] Rebased bin/ui/input on input-synthesis."
This reverts commit 062d53aad73ef798791968c34b5c6edf77412bb8.
Reason for revert: Breaks input swiping from shell which e2e screen navigation tests rely on.
Original change's description:
> [input] Rebased bin/ui/input on input-synthesis.
>
> Refactored bin/ui/input to make use of src/lib/ui/input-synthesis.
>
> TEST=Build, CQ and FIDL tests.
> fx run-test input-synthesis-tests
> fx run-test e2e_input_tests
>
> Change-Id: Iff14de9f87247d88dad7d729925c59cfc8af698e
TBR=qsr@google.com,dtiselice@google.com,viktard@google.com,jaeheon@google.com
Change-Id: I6bf9b8ac9ce6d1e2499c13703d0ce04e8ee3d4a0
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
diff --git a/garnet/bin/ui/BUILD.gn b/garnet/bin/ui/BUILD.gn
index 429564f..5ace26c 100644
--- a/garnet/bin/ui/BUILD.gn
+++ b/garnet/bin/ui/BUILD.gn
@@ -41,6 +41,7 @@
test_package("scenic_tests") {
deps = [
+ "input/tests",
"input_reader/tests",
"presentation_mode/tests",
"root_presenter/tests",
@@ -89,6 +90,10 @@
environments = basic_envs
},
{
+ name = "input_tool_unittests"
+ environments = basic_envs
+ },
+ {
name = "presentation_mode_unittests"
environments = basic_envs
},
diff --git a/garnet/bin/ui/input/BUILD.gn b/garnet/bin/ui/input/BUILD.gn
index 02af3e9..e00ee0d 100644
--- a/garnet/bin/ui/input/BUILD.gn
+++ b/garnet/bin/ui/input/BUILD.gn
@@ -1,17 +1,34 @@
-
-# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Copyright 2017 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.
-import("//build/rust/rustc_binary.gni")
+executable("input") {
+ sources = [
+ "main.cc",
+ ]
-rustc_binary("input") {
- name = "input"
- version = "0.1.0"
- edition = "2018"
deps = [
- "//garnet/public/rust/fuchsia-async",
- "//src/lib/ui/input-synthesis",
- "//third_party/rust_crates:structopt",
+ ":inverse_keymap",
+ "//garnet/public/lib/fsl",
+ "//garnet/public/lib/ui/input/cpp",
+ "//sdk/fidl/fuchsia.ui.input",
+ "//sdk/lib/sys/cpp",
+ "//src/lib/fxl",
+ "//zircon/public/lib/async-loop-cpp",
+ "//zircon/public/lib/hid",
+ "//zircon/public/lib/trace",
+ "//zircon/public/lib/trace-provider-with-fdio",
+ ]
+}
+
+source_set("inverse_keymap") {
+ sources = [
+ "inverse_keymap.cc",
+ "inverse_keymap.h",
+ ]
+
+ public_deps = [
+ "//sdk/fidl/fuchsia.ui.input",
+ "//zircon/public/lib/hid",
]
}
diff --git a/garnet/bin/ui/input/inverse_keymap.cc b/garnet/bin/ui/input/inverse_keymap.cc
new file mode 100644
index 0000000..0e88c6e
--- /dev/null
+++ b/garnet/bin/ui/input/inverse_keymap.cc
@@ -0,0 +1,89 @@
+// 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/inverse_keymap.h"
+
+namespace input {
+
+InverseKeymap InvertKeymap(const keychar_t keymap[]) {
+ InverseKeymap inverse;
+
+ for (uint32_t usage = 0; usage < KEYMAP_SIZE; ++usage) {
+ const keychar_t& mapping = keymap[usage];
+ if (mapping.c) {
+ Keystroke& keystroke = inverse[mapping.c];
+ keystroke.usage = usage;
+ keystroke.shift = mapping.c == mapping.shift_c
+ ? Keystroke::Shift::kDontCare
+ : Keystroke::Shift::kNo;
+ }
+
+ if (mapping.shift_c && mapping.shift_c != mapping.c) {
+ inverse[mapping.shift_c] = {usage, Keystroke::Shift::kYes};
+ }
+ }
+
+ return inverse;
+}
+
+std::pair<KeySequence, bool> DeriveKeySequence(
+ const InverseKeymap& inverse_keymap, const std::string& text) {
+ uint32_t last_key = 0;
+ bool shift = false;
+ KeySequence key_sequence;
+ key_sequence.reserve(text.length() + 1);
+
+ for (auto next = text.begin(); next != text.end();) {
+ auto report = fuchsia::ui::input::KeyboardReport::New();
+ report->pressed_keys.resize(0);
+
+ const auto it = inverse_keymap.find(*next);
+ if (it == inverse_keymap.end()) {
+ return {KeySequence(), false};
+ }
+
+ const Keystroke& keystroke = it->second;
+
+ if (keystroke.shift == Keystroke::Shift::kYes && !shift) {
+ shift = true;
+ last_key = 0;
+ } else if (keystroke.shift == Keystroke::Shift::kNo && shift) {
+ shift = false;
+ last_key = 0;
+ } else {
+ // If the shift key changes, we need to send its transition separately to
+ // guarantee clients handle it as expected, so only process other keys if
+ // there's no shift transition.
+
+ if (last_key == keystroke.usage) {
+ // If the key is already down, clear it first.
+ // (We can go ahead and send the next shift-key state below though).
+ last_key = 0;
+ } else {
+ report->pressed_keys.push_back(keystroke.usage);
+ last_key = keystroke.usage;
+ ++next;
+ }
+ }
+
+ // HID_USAGE_KEY_LEFT_SHIFT > all symbolic keys, and I believe the real impl
+ // always sends keys in ascending order, so do this last.
+ if (shift) {
+ report->pressed_keys.push_back(HID_USAGE_KEY_LEFT_SHIFT);
+ }
+
+ key_sequence.emplace_back(std::move(report));
+ }
+
+ // Make sure we end on an empty report.
+ if (key_sequence.size() > 0) {
+ auto report = fuchsia::ui::input::KeyboardReport::New();
+ report->pressed_keys.resize(0);
+ key_sequence.emplace_back(std::move(report));
+ }
+
+ return {std::move(key_sequence), true};
+}
+
+} // namespace input
diff --git a/garnet/bin/ui/input/inverse_keymap.h b/garnet/bin/ui/input/inverse_keymap.h
new file mode 100644
index 0000000..e97b741
--- /dev/null
+++ b/garnet/bin/ui/input/inverse_keymap.h
@@ -0,0 +1,62 @@
+// 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.
+
+#ifndef GARNET_BIN_UI_INPUT_INVERSE_KEYMAP_H_
+#define GARNET_BIN_UI_INPUT_INVERSE_KEYMAP_H_
+
+#include <map>
+#include <utility>
+#include <vector>
+
+#include <fuchsia/ui/input/cpp/fidl.h>
+#include <hid/hid.h>
+#include <hid/usages.h>
+
+namespace input {
+
+struct Keystroke {
+ uint32_t usage;
+ enum class Shift {
+ kNo,
+ kYes,
+ kDontCare,
+ } shift;
+};
+
+// Lightweight utility for basic keymap conversion of chars to keystrokes. This
+// is intended for end-to-end and input testing only; for production use cases
+// and general testing, IME injection should be used instead. Generally a
+// mapping exists only for printable ASCII characters; in particular neither \t
+// nor \n is mapped in either of the standard zircon keymaps. Furthermore, IME
+// implementations may themselves override the keymap in a way that invalidates
+// this translation.
+//
+// This is an inverse of hid/hid.h:hid_map_key.
+using InverseKeymap = std::map<char, Keystroke>;
+using KeySequence = std::vector<fuchsia::ui::input::KeyboardReportPtr>;
+
+// Constructs an inverse keymap from a keymap with |KEYMAP_SIZE| entries.
+InverseKeymap InvertKeymap(const keychar_t keymap[]);
+
+// Builds a key sequence representing the given string under the provided
+// |InverseKeymap|.
+//
+// This is intended for end-to-end and input testing only; for production use
+// cases and general testing, IME injection should be used instead.
+//
+// A translation from |text| to a sequence of keystrokes is not guaranteed to
+// exist. If a translation does not exist, false is returned for the bool
+// member of the pair. See |InverseKeymap| for details.
+//
+// The sequence does not contain pauses except between repeated keys or to clear
+// a shift state, though the sequence does terminate with an empty report (no
+// keys pressed). A shift key transition is sent in advance of each series of
+// keys that needs it.
+//
+// Returns { key sequence, success }.
+std::pair<KeySequence, bool> DeriveKeySequence(
+ const InverseKeymap& inverse_keymap, const std::string& text);
+
+} // namespace input
+#endif // GARNET_BIN_UI_INPUT_INVERSE_KEYMAP_H_
diff --git a/garnet/bin/ui/input/main.cc b/garnet/bin/ui/input/main.cc
new file mode 100644
index 0000000..2e41b9c
--- /dev/null
+++ b/garnet/bin/ui/input/main.cc
@@ -0,0 +1,626 @@
+// Copyright 2017 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 <fuchsia/ui/input/cpp/fidl.h>
+#include <hid/usages.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/async/cpp/task.h>
+#include <lib/async/default.h>
+#include <lib/sys/cpp/component_context.h>
+#include <lib/ui/input/cpp/formatting.h>
+#include <lib/zx/time.h>
+#include <trace-provider/provider.h>
+#include <trace/event.h>
+
+#include <iostream>
+#include <memory>
+
+#include "garnet/bin/ui/input/inverse_keymap.h"
+#include "src/lib/fxl/command_line.h"
+#include "src/lib/fxl/log_settings.h"
+#include "src/lib/fxl/log_settings_command_line.h"
+#include "src/lib/fxl/logging.h"
+#include "src/lib/fxl/strings/string_number_conversions.h"
+#include "src/lib/fxl/time/time_point.h"
+
+namespace {
+
+int64_t InputEventTimestampNow() {
+ return fxl::TimePoint::Now().ToEpochDelta().ToNanoseconds();
+}
+
+} // namespace
+
+namespace input {
+class InputApp {
+ public:
+ InputApp(async::Loop* loop)
+ : loop_(loop), component_context_(sys::ComponentContext::Create()) {
+ registry_ = component_context_->svc()
+ ->Connect<fuchsia::ui::input::InputDeviceRegistry>();
+ }
+
+ ~InputApp() {}
+
+ void Run(const fxl::CommandLine& command_line) {
+ const auto& positional_args = command_line.positional_args();
+ if (positional_args.empty()) {
+ Usage();
+ return;
+ }
+
+ zx::duration duration;
+ {
+ std::string duration_str;
+ if (command_line.GetOptionValue("duration", &duration_str)) {
+ uint32_t duration_ms;
+ if (!fxl::StringToNumberWithError(duration_str, &duration_ms)) {
+ Error("Invalid duration parameter");
+ return;
+ }
+ duration = zx::msec(duration_ms);
+ }
+ }
+
+ if (positional_args[0] == "tap" || positional_args[0] == "swipe") {
+ uint32_t width = 1000;
+ std::string width_str;
+ if (command_line.GetOptionValue("width", &width_str)) {
+ if (!fxl::StringToNumberWithError(width_str, &width)) {
+ Error("Invalid width parameter");
+ return;
+ }
+ }
+
+ uint32_t height = 1000;
+ std::string height_str;
+ if (command_line.GetOptionValue("height", &height_str)) {
+ if (!fxl::StringToNumberWithError(height_str, &height)) {
+ Error("Invalid height parameter");
+ return;
+ }
+ }
+
+ input_device_ = RegisterTouchscreen(width, height);
+
+ if (positional_args[0] == "tap") {
+ uint32_t tap_event_count = 1;
+ std::string tap_event_count_str;
+ if (command_line.GetOptionValue("tap_event_count",
+ &tap_event_count_str)) {
+ if (!fxl::StringToNumberWithError(tap_event_count_str,
+ &tap_event_count)) {
+ Error("Invalid tap_event_count parameter");
+ return;
+ }
+ }
+ TapEventCommand(positional_args, duration, tap_event_count);
+ } else { // "swipe"
+ uint32_t move_event_count = 100;
+ std::string move_event_count_str;
+ if (command_line.GetOptionValue("move_event_count",
+ &move_event_count_str)) {
+ if (!fxl::StringToNumberWithError(move_event_count_str,
+ &move_event_count)) {
+ Error("Invalid move_event_count parameter");
+ return;
+ }
+ }
+ SwipeEventCommand(positional_args, duration, move_event_count);
+ }
+ } else if (positional_args[0] == "keyevent") {
+ KeyEventCommand(positional_args, duration);
+ } else if (positional_args[0] == "text") {
+ TextCommand(positional_args, duration);
+ } else if (positional_args[0] == "media_button") {
+ MediaButtonEventCommand(positional_args);
+ } else {
+ Usage();
+ }
+ }
+
+ private:
+ void Usage() {
+ // Keep this up to date with README.md.
+ // Until we have standardized usage doc formatting, let's do 100 cols.
+ std::cout
+ << R"END(usage: input [<options>] text|keyevent|tap|swipe|media_button <args>
+ input text <text>
+ input keyevent <hid_usage (int)>
+ input tap <x> <y>
+ input swipe <x0> <y0> <x1> <y1>
+ input media_button <mic_mute> <volume_up> <volume_down> <reset>
+
+global options:
+ --duration=<ms> the duration of the event, in milliseconds (default: 0)
+
+commands:
+ text Text is injected by translating to keystrokes using a QWERTY
+ keymap. Only simple strings are supported; see README.md for
+ details.
+
+ The --duration option is divided over the key events. Care should
+ be taken not to provide so long a duration that key repeat kicks
+ in.
+
+ Note: when using through fx shell with quotes, you may need to
+ surround the invocation in strong quotes, e.g.:
+ fx shell 'input text "Hello, world!"'
+
+ keyevent Common usage codes:
+
+ key | code (dec)
+ ----------|-----
+ enter | 40
+ escape | 41
+ backspace | 42
+ tab | 43
+
+ tap/swipe By default, the x and y coordinates are in the range 0 to 1000
+ and will be proportionally transformed to the current display,
+ but you can specify a virtual range for the input with the
+ --width and --height options.
+
+ media_button Sends a MediaButton event. All fields are booleans and must
+ be either 0 or 1.
+
+ options:
+ --width=<w> the width of the display (default: 1000)
+ --height=<h> the height of the display (default: 1000)
+
+ swipe options:
+ --move_event_count=<count> the number of move events to send in between the down and up
+ events of the swipe (default: 100)
+
+ --tap_event_count=<count> the number of tap events to send (default: 1)
+ The --duration option is divided over the tap events.
+
+For further details, see README.md.
+)END";
+
+ loop_->Quit();
+ }
+
+ void Error(std::string message) {
+ std::cout << message << std::endl;
+ loop_->Quit();
+ }
+
+ fuchsia::ui::input::InputDevicePtr RegisterTouchscreen(uint32_t width,
+ uint32_t height) {
+ fuchsia::ui::input::InputDevicePtr input_device;
+
+ fuchsia::ui::input::TouchscreenDescriptorPtr touchscreen =
+ fuchsia::ui::input::TouchscreenDescriptor::New();
+ touchscreen->x.range.min = 0;
+ touchscreen->x.range.max = width;
+
+ touchscreen->y.range.min = 0;
+ touchscreen->y.range.max = height;
+
+ fuchsia::ui::input::DeviceDescriptor descriptor;
+ descriptor.touchscreen = std::move(touchscreen);
+
+ FXL_VLOG(1) << "Registering " << descriptor;
+ registry_->RegisterDevice(std::move(descriptor), input_device.NewRequest());
+ return input_device;
+ }
+
+ fuchsia::ui::input::InputDevicePtr RegisterKeyboard() {
+ fuchsia::ui::input::KeyboardDescriptorPtr keyboard =
+ fuchsia::ui::input::KeyboardDescriptor::New();
+ keyboard->keys.reserve(HID_USAGE_KEY_RIGHT_GUI - HID_USAGE_KEY_A);
+ for (uint32_t usage = HID_USAGE_KEY_A; usage < HID_USAGE_KEY_RIGHT_GUI;
+ ++usage) {
+ keyboard->keys.push_back(usage);
+ }
+ fuchsia::ui::input::DeviceDescriptor descriptor;
+ descriptor.keyboard = std::move(keyboard);
+
+ fuchsia::ui::input::InputDevicePtr input_device;
+ FXL_VLOG(1) << "Registering " << descriptor;
+ registry_->RegisterDevice(std::move(descriptor), input_device.NewRequest());
+ return input_device;
+ }
+
+ fuchsia::ui::input::InputDevicePtr RegisterMediaButtons() {
+ fuchsia::ui::input::MediaButtonsDescriptorPtr mediabuttons =
+ fuchsia::ui::input::MediaButtonsDescriptor::New();
+ mediabuttons->buttons = fuchsia::ui::input::kMicMute |
+ fuchsia::ui::input::kVolumeUp |
+ fuchsia::ui::input::kVolumeDown;
+ fuchsia::ui::input::DeviceDescriptor descriptor;
+ descriptor.media_buttons = std::move(mediabuttons);
+
+ fuchsia::ui::input::InputDevicePtr input_device;
+ FXL_VLOG(1) << "Registering " << descriptor;
+ registry_->RegisterDevice(std::move(descriptor), input_device.NewRequest());
+ return input_device;
+ }
+
+ void MediaButtonEventCommand(const std::vector<std::string>& args) {
+ if (args.size() != 5) {
+ Usage();
+ return;
+ }
+
+ int32_t mic_mute, volume_up, volume_down, reset;
+ if (!fxl::StringToNumberWithError(args[1], &mic_mute)) {
+ Error("Invalid mic_mute number");
+ return;
+ }
+ if (mic_mute != 0 && mic_mute != 1) {
+ Error("mic_mute must be 0 or 1");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[2], &volume_up)) {
+ Error("Invalid volume_up number");
+ return;
+ }
+ if (volume_up < 0 || volume_up > 1) {
+ Error("volume_up must be 0 or 1");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[3], &volume_down)) {
+ Error("Invalid volume_down number");
+ return;
+ }
+ if (volume_down < 0 || volume_down > 1) {
+ Error("volume_down must be 0 or 1");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[4], &reset)) {
+ Error("Invalid reset number");
+ return;
+ }
+ if (reset < 0 || reset > 1) {
+ Error("reset must be 0 or 1");
+ return;
+ }
+
+ fuchsia::ui::input::InputDevicePtr input_device = RegisterMediaButtons();
+ SendMediaButton(std::move(input_device), mic_mute, volume_up, volume_down,
+ reset);
+ }
+
+ void SendMediaButton(fuchsia::ui::input::InputDevicePtr input_device,
+ bool mic_mute, bool volume_up, bool volume_down,
+ bool reset) {
+ TRACE_DURATION("input", "SendMediaButton");
+ fuchsia::ui::input::MediaButtonsReportPtr media_buttons =
+ fuchsia::ui::input::MediaButtonsReport::New();
+ media_buttons->mic_mute = mic_mute;
+ media_buttons->volume_up = volume_up;
+ media_buttons->volume_down = volume_down;
+ media_buttons->reset = reset;
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.media_buttons = std::move(media_buttons);
+
+ FXL_VLOG(1) << "SendMediaButton " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device->DispatchReport(std::move(report));
+ loop_->Quit();
+ }
+
+ void TapEventCommand(const std::vector<std::string>& args,
+ zx::duration duration, uint32_t tap_event_count) {
+ if (args.size() != 3) {
+ Usage();
+ return;
+ }
+
+ int32_t x, y;
+
+ if (!fxl::StringToNumberWithError(args[1], &x)) {
+ Error("Invalid x coordinate");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[2], &y)) {
+ Error("Invalid y coordinate");
+ return;
+ }
+
+ FXL_VLOG(1) << "TapEvent " << x << "x" << y;
+
+ zx::duration tap_duration = duration;
+ if (tap_event_count > 1)
+ tap_duration = duration / tap_event_count;
+
+ SendTap(x, y, tap_duration, tap_event_count, 0);
+ }
+
+ void KeyEventCommand(const std::vector<std::string>& args,
+ zx::duration duration) {
+ if (args.size() != 2) {
+ Usage();
+ return;
+ }
+
+ uint32_t usage;
+
+ if (!fxl::StringToNumberWithError(args[1], &usage)) {
+ Error("Invalid HID usage value");
+ return;
+ }
+
+ if (usage < HID_USAGE_KEY_A || usage > HID_USAGE_KEY_RIGHT_GUI) {
+ Error("Invalid HID usage value");
+ return;
+ }
+
+ FXL_VLOG(1) << "KeyEvent " << usage;
+
+ fuchsia::ui::input::InputDevicePtr input_device = RegisterKeyboard();
+ SendKeyPress(std::move(input_device), usage, duration);
+ }
+
+ void TextCommand(const std::vector<std::string>& args,
+ zx::duration duration) {
+ if (args.size() != 2) {
+ Usage();
+ return;
+ }
+
+ // TODO(SCN-1068): Default to IME-based input, and have the current mode
+ // available as an option.
+ // TODO(SCN-1068): Pull default keymap from environment if possible.
+ KeySequence key_sequence;
+ bool ok;
+ std::tie(key_sequence, ok) =
+ DeriveKeySequence(InvertKeymap(qwerty_map), args[1]);
+ if (!ok) {
+ Error("Cannot translate text to key sequence");
+ return;
+ }
+
+ FXL_VLOG(1) << "Text " << args[1];
+
+ fuchsia::ui::input::InputDevicePtr input_device = RegisterKeyboard();
+ SendText(std::move(input_device), std::move(key_sequence), duration);
+ }
+
+ void SwipeEventCommand(const std::vector<std::string>& args,
+ zx::duration duration, uint32_t move_event_count) {
+ if (args.size() != 5) {
+ Usage();
+ return;
+ }
+
+ int32_t x0, y0, x1, y1;
+
+ if (!fxl::StringToNumberWithError(args[1], &x0)) {
+ Error("Invalid x0 coordinate");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[2], &y0)) {
+ Error("Invalid y0 coordinate");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[3], &x1)) {
+ Error("Invalid x1 coordinate");
+ return;
+ }
+ if (!fxl::StringToNumberWithError(args[4], &y1)) {
+ Error("Invalid y1 coordinate");
+ return;
+ }
+
+ FXL_VLOG(1) << "SwipeEvent " << x0 << "x" << y0 << " -> " << x1 << "x"
+ << y1;
+
+ SendSwipe(x0, y0, x1, y1, duration, move_event_count);
+ }
+
+ void SendTap(int32_t x, int32_t y, zx::duration tap_duration,
+ uint32_t max_tap_count, uint32_t cur_tap_count) {
+ TRACE_DURATION("input", "SendTap");
+
+ // DOWN
+ fuchsia::ui::input::Touch touch;
+ touch.finger_id = 1;
+ touch.x = x;
+ touch.y = y;
+ fuchsia::ui::input::TouchscreenReportPtr touchscreen =
+ fuchsia::ui::input::TouchscreenReport::New();
+ touchscreen->touches.push_back(std::move(touch));
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.touchscreen = std::move(touchscreen);
+
+ FXL_VLOG(1) << "SendTap " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device_->DispatchReport(std::move(report));
+
+ async::PostDelayedTask(
+ async_get_default_dispatcher(),
+ [this, x, y, tap_duration, max_tap_count, cur_tap_count]() mutable {
+ TRACE_DURATION("input", "SendTap");
+ // UP
+ fuchsia::ui::input::TouchscreenReportPtr touchscreen =
+ fuchsia::ui::input::TouchscreenReport::New();
+ touchscreen->touches.resize(0);
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.touchscreen = std::move(touchscreen);
+
+ FXL_VLOG(1) << "SendTap " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device_->DispatchReport(std::move(report));
+ if (++cur_tap_count >= max_tap_count) {
+ loop_->Quit();
+ return;
+ }
+ SendTap(x, y, tap_duration, max_tap_count, cur_tap_count);
+ },
+ tap_duration);
+ }
+
+ void SendKeyPress(fuchsia::ui::input::InputDevicePtr input_device,
+ uint32_t usage, zx::duration duration) {
+ TRACE_DURATION("input", "SendKeyPress");
+ // PRESSED
+ fuchsia::ui::input::KeyboardReportPtr keyboard =
+ fuchsia::ui::input::KeyboardReport::New();
+ keyboard->pressed_keys.push_back(usage);
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.keyboard = std::move(keyboard);
+ FXL_VLOG(1) << "SendKeyPress " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device->DispatchReport(std::move(report));
+
+ async::PostDelayedTask(
+ async_get_default_dispatcher(),
+ [this, device = std::move(input_device)] {
+ TRACE_DURATION("input", "SendKeyPress");
+ // RELEASED
+ fuchsia::ui::input::KeyboardReportPtr keyboard =
+ fuchsia::ui::input::KeyboardReport::New();
+ keyboard->pressed_keys.resize(0);
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.keyboard = std::move(keyboard);
+ FXL_VLOG(1) << "SendKeyPress " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ device->DispatchReport(std::move(report));
+ loop_->Quit();
+ },
+ duration);
+ }
+
+ void SendText(fuchsia::ui::input::InputDevicePtr input_device,
+ std::vector<fuchsia::ui::input::KeyboardReportPtr> key_sequence,
+ zx::duration duration, size_t at = 0) {
+ TRACE_DURATION("input", "SendText");
+ if (at >= key_sequence.size()) {
+ loop_->Quit();
+ return;
+ }
+ fuchsia::ui::input::KeyboardReportPtr keyboard =
+ std::move(key_sequence[at]);
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.keyboard = std::move(keyboard);
+ FXL_VLOG(1) << "SendText " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device->DispatchReport(std::move(report));
+
+ zx::duration stroke_duration;
+ if (key_sequence.size() > 1) {
+ stroke_duration = duration / (key_sequence.size() - 1);
+ }
+
+ async::PostDelayedTask(
+ async_get_default_dispatcher(),
+ [this, device = std::move(input_device),
+ key_sequence = std::move(key_sequence), duration, at]() mutable {
+ SendText(std::move(device), std::move(key_sequence), duration,
+ at + 1);
+ },
+ stroke_duration);
+ // If the sequence is empty at this point, the next iteration (Quit) is
+ // scheduled asap.
+ }
+
+ void SendSwipe(int32_t x0, int32_t y0, int32_t x1, int32_t y1,
+ zx::duration duration, uint32_t move_event_count) {
+ TRACE_DURATION("input", "SendSwipe");
+
+ zx::duration swipe_event_delay = duration;
+ float deltaX = (x1 - x0);
+ float deltaY = (y1 - y0);
+ if (move_event_count > 1) {
+ // We have move_event_count + 2 events:
+ // DOWN
+ // MOVE x move_event_count
+ // UP
+ // so we need (move_event_count + 1) delays.
+ swipe_event_delay = duration / (move_event_count + 1);
+ deltaX = deltaX / move_event_count;
+ deltaY = deltaY / move_event_count;
+ }
+
+ // DOWN
+ SendTouchEvent(x0, y0);
+
+ for (int32_t i = 1; i <= (int32_t)move_event_count; i++) {
+ // MOVE
+ async::PostDelayedTask(
+ async_get_default_dispatcher(),
+ [this, i, x0, y0, deltaX, deltaY] {
+ SendTouchEvent(x0 + round(i * deltaX), y0 + round(i * deltaY));
+ },
+ swipe_event_delay * i);
+ }
+
+ async::PostDelayedTask(
+ async_get_default_dispatcher(),
+ [this] {
+ TRACE_DURATION("input", "SendSwipe");
+
+ // UP
+ fuchsia::ui::input::TouchscreenReportPtr touchscreen =
+ fuchsia::ui::input::TouchscreenReport::New();
+ touchscreen->touches.resize(0);
+
+ fuchsia::ui::input::InputReport report =
+ fuchsia::ui::input::InputReport();
+ report.event_time = InputEventTimestampNow();
+ report.touchscreen = std::move(touchscreen);
+ FXL_VLOG(1) << "SendSwipe " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device_->DispatchReport(std::move(report));
+
+ loop_->Quit();
+ },
+ duration);
+ }
+
+ void SendTouchEvent(int32_t x, int32_t y) {
+ TRACE_DURATION("input", "SendSwipe");
+ fuchsia::ui::input::Touch touch;
+ touch.finger_id = 1;
+
+ touch.x = x;
+ touch.y = y;
+
+ fuchsia::ui::input::TouchscreenReportPtr touchscreen =
+ fuchsia::ui::input::TouchscreenReport::New();
+ touchscreen->touches.push_back(std::move(touch));
+
+ fuchsia::ui::input::InputReport report;
+ report.event_time = InputEventTimestampNow();
+ report.touchscreen = std::move(touchscreen);
+ FXL_VLOG(1) << "SendSwipe " << report;
+ TRACE_FLOW_BEGIN("input", "hid_read_to_listener", report.trace_id);
+ input_device_->DispatchReport(std::move(report));
+ }
+
+ async::Loop* const loop_;
+ std::unique_ptr<sys::ComponentContext> component_context_;
+ fidl::InterfacePtr<fuchsia::ui::input::InputDeviceRegistry> registry_;
+ fuchsia::ui::input::InputDevicePtr input_device_;
+};
+} // namespace input
+
+int main(int argc, char** argv) {
+ auto command_line = fxl::CommandLineFromArgcArgv(argc, argv);
+ if (!fxl::SetLogSettingsFromCommandLine(command_line))
+ return 1;
+
+ async::Loop loop(&kAsyncLoopConfigAttachToThread);
+ input::InputApp app(&loop);
+ async::PostTask(loop.dispatcher(),
+ [&app, command_line] { app.Run(command_line); });
+ trace::TraceProviderWithFdio trace_provider(loop.dispatcher(), "input");
+ loop.Run();
+ return 0;
+}
diff --git a/garnet/bin/ui/input/src/main.rs b/garnet/bin/ui/input/src/main.rs
deleted file mode 100644
index fb5fccf..0000000
--- a/garnet/bin/ui/input/src/main.rs
+++ /dev/null
@@ -1,207 +0,0 @@
-use std::{env, time::Duration};
-
-use fuchsia_async as fasync;
-
-use input_synthesis;
-
-use structopt::StructOpt;
-
-#[derive(Debug, StructOpt)]
-#[structopt(name = "input")]
-/// simple tool to inject input events into Scenic
-struct Opt {
- #[structopt(subcommand)]
- command: Command,
-}
-
-const KEYEVENT_HELP: &str = r#"Injects a single key event.
-
-This command simulates a single key down + up sequence. The argument is a decimal HID usage
-`code`, prior to any remapping the IME may do.
-
-Common usage codes:
-
-key | code
-----------|-----
-enter | 40
-escape | 41
-backspace | 42
-tab | 43
-
-The time between the down and up report is `--duration`."#;
-
-#[derive(Debug, StructOpt)]
-enum Command {
- #[structopt(name = "text")]
- /// Injects text using QWERTY keystrokes
- ///
- /// Text is injected by translating a string into keystrokes using a QWERTY keymap. This
- /// facility is intended for end-to-end and input testing purposes only.
- ///
- /// Only printable ASCII characters are mapped. Tab, newline, and other control haracters are
- /// not supported, and `keyevent` should be used instead.
- ///
- /// The events simulated consist of a series of keyboard reports, ending in a report with no
- /// keys. The number of reports is near the lower bound of reports needed to produce the
- /// requested string, minimizing pauses and shift-state transitions.
- ///
- /// The `--duration` is divided between the reports. Care should be taken not to provide so
- /// long a duration that key repeat kicks in.
- ///
- /// Note: when using through `fx shell` with quotes, you may need to surround the invocation in
- /// strong quotes, e.g.:
- ///
- /// fx shell 'input text "Hello, world!"'
- Text {
- #[structopt(short = "d", long = "duration", default_value = "0")]
- /// Duration of the event(s) in milliseconds
- duration: u32,
- /// Input text to inject
- input: String,
- },
- #[structopt(name = "keyevent", raw(help = "KEYEVENT_HELP"))]
- KeyEvent {
- #[structopt(short = "d", long = "duration", default_value = "0")]
- /// Duration of the event(s) in milliseconds
- duration: u32,
- /// HID usage code
- usage: u32,
- },
- #[structopt(name = "tap")]
- /// Injects a single tap event
- ///
- /// This command simulates a single touch down + up sequence. By default, the `x` and `y`
- /// coordinates are in the range 0 to 1000 and will be proportionally transformed to the
- /// current display, but you can specify a virtual range for the input with the `--width` and
- /// `--height` options.
- ///
- /// The time between the down and up report is `--duration`.
- Tap {
- #[structopt(short = "w", long = "width", default_value = "1000")]
- /// Width of the display
- width: u32,
- #[structopt(short = "h", long = "height", default_value = "1000")]
- /// Height of the display
- height: u32,
- #[structopt(short = "tc", long = "tap_event_count", default_value = "1")]
- /// Number of tap events to send (`--duration` is divided over the tap events)
- tap_event_count: usize,
- #[structopt(short = "d", long = "duration", default_value = "0")]
- /// Duration of the event(s) in milliseconds
- duration: u32,
- /// X axis coordinate
- x: u32,
- /// Y axis coordinate
- y: u32,
- },
- #[structopt(name = "swipe")]
- /// Injects a swipe event
- ///
- /// This command simulates a touch down, a series of touch move, followed up a touch up event.
- /// By default, the `x` and `y` coordinates are in the range 0 to 1000 and will be
- /// proportionally transformed to the current display, but you can specify a virtual range for
- /// the input with the `--width` and `--height` options.
- ///
- /// The time between the down and up report is `--duration`.
- Swipe {
- #[structopt(short = "w", long = "width", default_value = "1000")]
- /// Width of the display
- width: u32,
- #[structopt(short = "h", long = "height", default_value = "1000")]
- /// Height of the display
- height: u32,
- #[structopt(short = "mc", long = "move_event_count", default_value = "100")]
- /// Number of move events to send in between the down and up events of the swipe
- move_event_count: usize,
- #[structopt(short = "d", long = "duration", default_value = "0")]
- /// Duration of the event(s) in milliseconds
- duration: u32,
- /// X axis start coordinate
- x0: u32,
- /// Y axis start coordinate
- y0: u32,
- /// X axis end coordinate
- x1: u32,
- /// Y axis end coordinate
- y1: u32,
- },
- #[structopt(name = "media_button")]
- /// Injects a MediaButton event
- MediaButton {
- #[structopt(parse(try_from_str = "parse_bool"))]
- /// 0 or 1
- mic_mute: bool,
- #[structopt(parse(try_from_str = "parse_bool"))]
- /// 0 or 1
- volume_up: bool,
- #[structopt(parse(try_from_str = "parse_bool"))]
- /// 0 or 1
- volume_down: bool,
- #[structopt(parse(try_from_str = "parse_bool"))]
- /// 0 or 1
- reset: bool,
- },
-}
-
-fn parse_bool(src: &str) -> Result<bool, String> {
- match src {
- "0" | "false" => Ok(false),
- "1" | "true" => Ok(true),
- _ => Err(format!("cannot parse {:?} to bool", src)),
- }
-}
-
-macro_rules! run {
- ( $executor:expr, $command:ident, [ $( $args:expr ),* $( , )? ] ) => {
- $executor.run(
- input_synthesis::$command(
- $($args),*
- ),
- 1,
- ).expect("failed to run command");
- }
-}
-
-fn main() {
- // TODO: Remove this workaround once topaz tests have been updated.
- let (mut args, options): (Vec<_>, Vec<_>) = env::args_os().partition(|s| match s.to_str() {
- Some(s) => s.get(0..2) != Some("--"),
- _ => false,
- });
-
- args.extend(options);
-
- let opt = Opt::from_iter(args.into_iter());
- let mut executor = fasync::Executor::new().expect("failed to create executor");
-
- match opt.command {
- Command::Text { input, duration } => {
- run!(executor, text_command, [input, Duration::from_millis(duration as u64),])
- }
- Command::KeyEvent { usage, duration } => {
- run!(executor, key_event_command, [usage, Duration::from_millis(duration as u64),])
- }
- Command::Tap { width, height, tap_event_count, duration, x, y } => run!(
- executor,
- tap_event_command,
- [x, y, width, height, tap_event_count, Duration::from_millis(duration as u64),]
- ),
- Command::Swipe { width, height, move_event_count, duration, x0, y0, x1, y1 } => run!(
- executor,
- swipe_command,
- [
- x0,
- y0,
- x1,
- y1,
- width,
- height,
- move_event_count,
- Duration::from_millis(duration as u64),
- ]
- ),
- Command::MediaButton { mic_mute, volume_up, volume_down, reset } => {
- run!(executor, media_button_event_command, [mic_mute, volume_up, volume_down, reset,])
- }
- };
-}
diff --git a/garnet/bin/ui/input/tests/BUILD.gn b/garnet/bin/ui/input/tests/BUILD.gn
new file mode 100644
index 0000000..34dcdc9
--- /dev/null
+++ b/garnet/bin/ui/input/tests/BUILD.gn
@@ -0,0 +1,27 @@
+# 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.
+
+group("tests") {
+ testonly = true
+
+ public_deps = [
+ ":input_tool_unittests",
+ ]
+}
+
+executable("input_tool_unittests") {
+ output_name = "input_tool_unittests"
+
+ testonly = true
+
+ sources = [
+ "inverse_keymap_unittest.cc",
+ ]
+
+ deps = [
+ "//garnet/bin/ui/input:inverse_keymap",
+ "//src/lib/fxl/test:gtest_main",
+ "//zircon/public/lib/hid",
+ ]
+}
diff --git a/garnet/bin/ui/input/tests/inverse_keymap_unittest.cc b/garnet/bin/ui/input/tests/inverse_keymap_unittest.cc
new file mode 100644
index 0000000..6475198
--- /dev/null
+++ b/garnet/bin/ui/input/tests/inverse_keymap_unittest.cc
@@ -0,0 +1,149 @@
+// 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 <hid/hid.h>
+#include <hid/usages.h>
+
+#include "garnet/bin/ui/input/inverse_keymap.h"
+#include "gtest/gtest.h"
+
+namespace input {
+
+namespace {
+
+// Initializes an inverse QWERTY keymap.
+class InverseKeymapTest : public testing::Test {
+ protected:
+ InverseKeymapTest() : keymap_(InvertKeymap(qwerty_map)) {}
+
+ const InverseKeymap keymap_;
+};
+
+// A version of |Keystroke| with a more concise, binary shift state.
+struct SimpleKeystroke {
+ uint32_t usage;
+ bool shift;
+};
+
+// Runs checks on a key sequence, using |SimpleKeystroke|s as a convenience
+// representation. Null usages on the expectation indicate that no (non-shift)
+// key is expected to be pressed. (Shift is governed separately.)
+void CheckKeySequence(const KeySequence& actual,
+ const std::vector<SimpleKeystroke>& expected) {
+ ASSERT_EQ(actual.size(), expected.size());
+
+ for (size_t i = 0; i < actual.size(); ++i) {
+ const std::vector<uint32_t>& pressed_keys = actual[i]->pressed_keys;
+
+ uint32_t key = 0;
+ bool shift = false;
+ for (uint32_t usage : pressed_keys) {
+ EXPECT_NE(usage, 0u);
+
+ if (usage == HID_USAGE_KEY_LEFT_SHIFT) {
+ EXPECT_FALSE(shift) << "Duplicate shift key in report";
+ shift = true;
+ } else {
+ EXPECT_EQ(key, 0u) << "Multiple normal keys in report";
+ key = usage;
+ }
+ }
+
+ EXPECT_EQ(key, expected[i].usage);
+ EXPECT_EQ(shift, expected[i].shift);
+ }
+}
+
+} // namespace
+
+TEST_F(InverseKeymapTest, PlainKey) {
+ auto it = keymap_.find('a');
+ ASSERT_NE(it, keymap_.end());
+ const Keystroke& keystroke = it->second;
+ EXPECT_EQ(keystroke.usage, HID_USAGE_KEY_A);
+ EXPECT_EQ(keystroke.shift, Keystroke::Shift::kNo);
+}
+
+TEST_F(InverseKeymapTest, ShiftKey) {
+ auto it = keymap_.find('A');
+ ASSERT_NE(it, keymap_.end());
+ const Keystroke& keystroke = it->second;
+ EXPECT_EQ(keystroke.usage, HID_USAGE_KEY_A);
+ EXPECT_EQ(keystroke.shift, Keystroke::Shift::kYes);
+}
+
+// The primary facility under test in the following cases is
+// |DeriveKeySequence|. See the inverse_keymap.h/cc for details on expected
+// behavior.
+
+TEST_F(InverseKeymapTest, Lowercase) {
+ KeySequence key_sequence;
+ bool ok;
+ std::tie(key_sequence, ok) = DeriveKeySequence(keymap_, "lowercase");
+ ASSERT_TRUE(ok);
+
+ CheckKeySequence(key_sequence, {{HID_USAGE_KEY_L, false},
+ {HID_USAGE_KEY_O, false},
+ {HID_USAGE_KEY_W, false},
+ {HID_USAGE_KEY_E, false},
+ {HID_USAGE_KEY_R, false},
+ {HID_USAGE_KEY_C, false},
+ {HID_USAGE_KEY_A, false},
+ {HID_USAGE_KEY_S, false},
+ {HID_USAGE_KEY_E, false},
+ {}});
+}
+
+TEST_F(InverseKeymapTest, Sentence) {
+ KeySequence key_sequence;
+ bool ok;
+ std::tie(key_sequence, ok) = DeriveKeySequence(keymap_, "Hello, world!");
+ ASSERT_TRUE(ok);
+
+ CheckKeySequence(key_sequence, {{0, true},
+ {HID_USAGE_KEY_H, true},
+ {},
+ {HID_USAGE_KEY_E, false},
+ {HID_USAGE_KEY_L, false},
+ {},
+ {HID_USAGE_KEY_L, false},
+ {HID_USAGE_KEY_O, false},
+ {HID_USAGE_KEY_COMMA, false},
+ {HID_USAGE_KEY_SPACE, false},
+ {HID_USAGE_KEY_W, false},
+ {HID_USAGE_KEY_O, false},
+ {HID_USAGE_KEY_R, false},
+ {HID_USAGE_KEY_L, false},
+ {HID_USAGE_KEY_D, false},
+ {0, true},
+ {HID_USAGE_KEY_1, true},
+ {}});
+}
+
+TEST_F(InverseKeymapTest, HoldShift) {
+ KeySequence key_sequence;
+ bool ok;
+ std::tie(key_sequence, ok) = DeriveKeySequence(keymap_, "ALL'S WELL!");
+ ASSERT_TRUE(ok);
+
+ CheckKeySequence(key_sequence, {{0, true},
+ {HID_USAGE_KEY_A, true},
+ {HID_USAGE_KEY_L, true},
+ {0, true},
+ {HID_USAGE_KEY_L, true},
+ {},
+ {HID_USAGE_KEY_APOSTROPHE, false},
+ {0, true},
+ {HID_USAGE_KEY_S, true},
+ {HID_USAGE_KEY_SPACE, true},
+ {HID_USAGE_KEY_W, true},
+ {HID_USAGE_KEY_E, true},
+ {HID_USAGE_KEY_L, true},
+ {0, true},
+ {HID_USAGE_KEY_L, true},
+ {HID_USAGE_KEY_1, true},
+ {}});
+}
+
+} // namespace input
diff --git a/garnet/bin/ui/meta/input_tool_unittests.cmx b/garnet/bin/ui/meta/input_tool_unittests.cmx
new file mode 100644
index 0000000..ada8620
--- /dev/null
+++ b/garnet/bin/ui/meta/input_tool_unittests.cmx
@@ -0,0 +1,5 @@
+{
+ "program": {
+ "binary": "test/input_tool_unittests"
+ }
+}
diff --git a/garnet/tests/benchmarks/input_latency/run_simplest_app_benchmark.sh b/garnet/tests/benchmarks/input_latency/run_simplest_app_benchmark.sh
index ab9e3a6..ac53456 100644
--- a/garnet/tests/benchmarks/input_latency/run_simplest_app_benchmark.sh
+++ b/garnet/tests/benchmarks/input_latency/run_simplest_app_benchmark.sh
@@ -46,7 +46,7 @@
# Each tap will be 33.5ms apart, drifting 0.166ms against regular 60 fps
# vsync interval. 100 taps span the entire vsync interval 1 time at 100
# equidistant points.
- /bin/input tap 500 500 --tap_event_count=100 --duration=3350
+ /bin/input --tap_event_count=100 --duration=3350 tap 500 500
) &
# Start tracing.
diff --git a/garnet/tests/benchmarks/input_latency/run_yuv_to_image_pipe_benchmark.sh b/garnet/tests/benchmarks/input_latency/run_yuv_to_image_pipe_benchmark.sh
index d244b9c..b7324286 100644
--- a/garnet/tests/benchmarks/input_latency/run_yuv_to_image_pipe_benchmark.sh
+++ b/garnet/tests/benchmarks/input_latency/run_yuv_to_image_pipe_benchmark.sh
@@ -48,7 +48,7 @@
# Each tap will be 33.5ms apart, drifting 0.166ms against regular 60 fps
# vsync interval. 100 taps span the entire vsync interval 1 time at 100
# equidistant points.
- /bin/input tap 500 500 --tap_event_count=100 --duration=3350
+ /bin/input --tap_event_count=100 --duration=3350 tap 500 500
) &
# Start tracing.
diff --git a/src/lib/ui/input-synthesis/BUILD.gn b/src/lib/ui/input-synthesis/BUILD.gn
index d467cb8a..145e58f 100644
--- a/src/lib/ui/input-synthesis/BUILD.gn
+++ b/src/lib/ui/input-synthesis/BUILD.gn
@@ -12,11 +12,9 @@
edition = "2018"
deps = [
"//garnet/public/lib/fidl/rust/fidl",
- "//garnet/public/rust/fuchsia-async",
"//garnet/public/rust/fuchsia-component",
"//sdk/fidl/fuchsia.ui.input:fuchsia.ui.input-rustc",
"//third_party/rust_crates:failure",
- "//third_party/rust_crates:futures-preview",
]
}
diff --git a/src/lib/ui/input-synthesis/src/lib.rs b/src/lib/ui/input-synthesis/src/lib.rs
index 0e20250..8eac917 100644
--- a/src/lib/ui/input-synthesis/src/lib.rs
+++ b/src/lib/ui/input-synthesis/src/lib.rs
@@ -11,7 +11,7 @@
use failure::Error;
-use fidl::endpoints::{self, ServerEnd};
+use fidl::endpoints;
use fidl_fuchsia_ui_input::{
self, Axis, AxisScale, DeviceDescriptor, InputDeviceMarker, InputDeviceProxy,
@@ -28,31 +28,8 @@
use crate::{inverse_keymap::InverseKeymap, usages::Usages};
-trait ServerConsumer {
- fn consume(
- &self,
- device: &mut DeviceDescriptor,
- server: ServerEnd<InputDeviceMarker>,
- ) -> Result<(), Error>;
-}
-
-struct RegistryServerConsumer;
-
-impl ServerConsumer for RegistryServerConsumer {
- fn consume(
- &self,
- device: &mut DeviceDescriptor,
- server: ServerEnd<InputDeviceMarker>,
- ) -> Result<(), Error> {
- let registry = app::client::connect_to_service::<InputDeviceRegistryMarker>()?;
- registry.register_device(device, server)?;
-
- Ok(())
- }
-}
-
macro_rules! register_device {
- ( $consumer:expr , $field:ident : $value:expr ) => {{
+ ( $field:ident : $value:expr ) => {{
let mut device = DeviceDescriptor {
device_info: None,
keyboard: None,
@@ -66,19 +43,15 @@
let (input_device_client, input_device_server) =
endpoints::create_endpoints::<InputDeviceMarker>()?;
- $consumer.consume(&mut device, input_device_server)?;
+ let registry = app::client::connect_to_service::<InputDeviceRegistryMarker>()?;
+ registry.register_device(&mut device, input_device_server)?;
Ok(input_device_client.into_proxy()?)
}};
}
-fn register_touchsreen(
- consumer: impl ServerConsumer,
- width: u32,
- height: u32,
-) -> Result<InputDeviceProxy, Error> {
+fn register_touchsreen(width: u32, height: u32) -> Result<InputDeviceProxy, Error> {
register_device! {
- consumer,
touchscreen: TouchscreenDescriptor {
x: Axis {
range: Range { min: 0, max: width as i32 },
@@ -95,18 +68,16 @@
}
}
-fn register_keyboard(consumer: impl ServerConsumer) -> Result<InputDeviceProxy, Error> {
+fn register_keyboard() -> Result<InputDeviceProxy, Error> {
register_device! {
- consumer,
keyboard: KeyboardDescriptor {
keys: (Usages::HidUsageKeyA as u32..Usages::HidUsageKeyRightGui as u32).collect(),
}
}
}
-fn register_media_buttons(consumer: impl ServerConsumer) -> Result<InputDeviceProxy, Error> {
+fn register_media_buttons() -> Result<InputDeviceProxy, Error> {
register_device! {
- consumer,
media_buttons: MediaButtonsDescriptor {
buttons: fidl_fuchsia_ui_input::MIC_MUTE
| fidl_fuchsia_ui_input::VOLUME_DOWN
@@ -161,14 +132,14 @@
}
}
-fn media_button_event(
+/// Simulates a media button event.
+pub async fn media_button_event_command(
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
- consumer: impl ServerConsumer,
) -> Result<(), Error> {
- let input_device = register_media_buttons(consumer)?;
+ let input_device = register_media_buttons()?;
input_device
.dispatch_report(&mut media_buttons(
@@ -181,14 +152,56 @@
.map_err(Into::into)
}
-/// Simulates a media button event.
-pub async fn media_button_event_command(
- volume_up: bool,
- volume_down: bool,
- mic_mute: bool,
- reset: bool,
+fn tap(pos: Option<(u32, u32)>, time: u64) -> InputReport {
+ InputReport {
+ event_time: time,
+ keyboard: None,
+ media_buttons: None,
+ mouse: None,
+ stylus: None,
+ touchscreen: Some(Box::new(TouchscreenReport {
+ touches: match pos {
+ Some((x, y)) => {
+ vec![Touch { finger_id: 1, x: x as i32, y: y as i32, width: 0, height: 0 }]
+ }
+ None => vec![],
+ },
+ })),
+ sensor: None,
+ trace_id: 0,
+ }
+}
+
+/// Simulates `tap_event_count` taps at coordinates `(x, y)` for a touchscreen of explicit
+/// `width` and `height`.
+///
+/// `duration` is divided equally between touch-down and touch-up event pairs, while the
+/// transition between these pairs is immediate.
+pub async fn tap_event_command(
+ x: u32,
+ y: u32,
+ width: u32,
+ height: u32,
+ duration: Duration,
+ tap_event_count: usize,
) -> Result<(), Error> {
- media_button_event(volume_up, volume_down, mic_mute, reset, RegistryServerConsumer)
+ let input_device = register_touchsreen(width, height)?;
+ let tap_duration = duration / tap_event_count as u32;
+
+ repeat_with_delay(
+ tap_event_count,
+ tap_duration,
+ |_| {
+ // Touch down.
+ input_device
+ .dispatch_report(&mut tap(Some((x, y)), nanos_from_epoch()?))
+ .map_err(Into::into)
+ },
+ |_| {
+ // Touch up.
+ input_device.dispatch_report(&mut tap(None, nanos_from_epoch()?)).map_err(Into::into)
+ },
+ )
}
fn key_press(keyboard: KeyboardReport, time: u64) -> InputReport {
@@ -216,8 +229,11 @@
)
}
-fn key_event(usage: u32, duration: Duration, consumer: impl ServerConsumer) -> Result<(), Error> {
- let input_device = register_keyboard(consumer)?;
+/// Simulates a key press of specified `usage`.
+///
+/// `duration` is the time spent between key-press and key-release events.
+pub async fn key_event_command(usage: u32, duration: Duration) -> Result<(), Error> {
+ let input_device = register_keyboard()?;
repeat_with_delay(
1,
@@ -237,17 +253,15 @@
)
}
-/// Simulates a key press of specified `usage`.
+/// Simulates `text` being typed on a [qwerty] keyboard by making use of [`InverseKeymap`].
///
-/// `duration` is the time spent between key-press and key-release events.
-pub async fn key_event_command(usage: u32, duration: Duration) -> Result<(), Error> {
- key_event(usage, duration, RegistryServerConsumer)
-}
-
-fn text(input: String, duration: Duration, consumer: impl ServerConsumer) -> Result<(), Error> {
- let input_device = register_keyboard(consumer)?;
+/// `duration` is divided equally between all keyboard events.
+///
+/// [qwerty]: keymaps/constant.QWERTY_MAP.html
+pub async fn text_command(text: String, duration: Duration) -> Result<(), Error> {
+ let input_device = register_keyboard()?;
let key_sequence = InverseKeymap::new(keymaps::QWERTY_MAP)
- .derive_key_sequence(&input)
+ .derive_key_sequence(&text)
.ok_or_else(|| failure::err_msg("Cannot translate text to key sequence"))?;
let stroke_duration = duration / (key_sequence.len() - 1) as u32;
@@ -267,80 +281,12 @@
Ok(())
}
-/// Simulates `input` being typed on a [qwerty] keyboard by making use of [`InverseKeymap`].
+/// Simulates swipe from coordinates `(x0, y0)` to `(x1, y1)` for a touchscreen of explicit
+/// `width` and `height`, with `move_event_count` touch-move events in between.
///
-/// `duration` is divided equally between all keyboard events.
-///
-/// [qwerty]: keymaps/constant.QWERTY_MAP.html
-pub async fn text_command(input: String, duration: Duration) -> Result<(), Error> {
- text(input, duration, RegistryServerConsumer)
-}
-
-fn tap(pos: Option<(u32, u32)>, time: u64) -> InputReport {
- InputReport {
- event_time: time,
- keyboard: None,
- media_buttons: None,
- mouse: None,
- stylus: None,
- touchscreen: Some(Box::new(TouchscreenReport {
- touches: match pos {
- Some((x, y)) => {
- vec![Touch { finger_id: 1, x: x as i32, y: y as i32, width: 0, height: 0 }]
- }
- None => vec![],
- },
- })),
- sensor: None,
- trace_id: 0,
- }
-}
-
-fn tap_event(
- x: u32,
- y: u32,
- width: u32,
- height: u32,
- tap_event_count: usize,
- duration: Duration,
- consumer: impl ServerConsumer,
-) -> Result<(), Error> {
- let input_device = register_touchsreen(consumer, width, height)?;
- let tap_duration = duration / tap_event_count as u32;
-
- repeat_with_delay(
- tap_event_count,
- tap_duration,
- |_| {
- // Touch down.
- input_device
- .dispatch_report(&mut tap(Some((x, y)), nanos_from_epoch()?))
- .map_err(Into::into)
- },
- |_| {
- // Touch up.
- input_device.dispatch_report(&mut tap(None, nanos_from_epoch()?)).map_err(Into::into)
- },
- )
-}
-
-/// Simulates `tap_event_count` taps at coordinates `(x, y)` for a touchscreen of explicit
-/// `width` and `height`.
-///
-/// `duration` is divided equally between touch-down and touch-up event pairs, while the
-/// transition between these pairs is immediate.
-pub async fn tap_event_command(
- x: u32,
- y: u32,
- width: u32,
- height: u32,
- tap_event_count: usize,
- duration: Duration,
-) -> Result<(), Error> {
- tap_event(x, y, width, height, tap_event_count, duration, RegistryServerConsumer)
-}
-
-fn swipe(
+/// `duration` is the time spent between the touch-down and first touch-move events when
+/// `move_event_count > 0` or between the touch-down the touch-up events otherwise.
+pub async fn swipe_command(
x0: u32,
y0: u32,
x1: u32,
@@ -349,9 +295,8 @@
height: u32,
move_event_count: usize,
duration: Duration,
- consumer: impl ServerConsumer,
) -> Result<(), Error> {
- let input_device = register_touchsreen(consumer, width, height)?;
+ let input_device = register_touchsreen(width, height)?;
let mut delta_x = x1 as f64 - x0 as f64;
let mut delta_y = y1 as f64 - y0 as f64;
@@ -386,7 +331,7 @@
time,
),
// UP
- _ => tap(None, time),
+ _ => tap(Some((x1, y1)), time),
};
input_device.dispatch_report(&mut report).map_err(Into::into)
@@ -394,214 +339,3 @@
|_| Ok(()),
)
}
-
-/// Simulates swipe from coordinates `(x0, y0)` to `(x1, y1)` for a touchscreen of explicit
-/// `width` and `height`, with `move_event_count` touch-move events in between.
-///
-/// `duration` is the time spent between the touch-down and first touch-move events when
-/// `move_event_count > 0` or between the touch-down the touch-up events otherwise.
-pub async fn swipe_command(
- x0: u32,
- y0: u32,
- x1: u32,
- y1: u32,
- width: u32,
- height: u32,
- move_event_count: usize,
- duration: Duration,
-) -> Result<(), Error> {
- swipe(x0, y0, x1, y1, width, height, move_event_count, duration, RegistryServerConsumer)
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- use fidl_fuchsia_ui_input::InputDeviceRequest;
-
- use fuchsia_async as fasync;
-
- use futures::TryStreamExt;
-
- macro_rules! assert_reports_eq {
- (
- $report:ident ,
- $event:expr ,
- [ $( { $name:ident : $value:expr } ),* $( , )? ]
- $( , )?
- ) => {{
- let mut executor = fasync::Executor::new().expect("failed to create executor");
-
- struct $report;
-
- impl ServerConsumer for $report {
- fn consume(
- &self,
- _: &mut DeviceDescriptor,
- server: ServerEnd<InputDeviceMarker>,
- ) -> Result<(), Error> {
- fasync::spawn(async move {
- let mut stream = server.into_stream().expect("failed to convert to stream");
-
- $(
- let option = await!(stream.try_next())
- .expect("failed to await on the next element");
- assert!(option.is_some(), "stream should not be empty");
-
- if let Some(request) = option {
- let InputDeviceRequest::DispatchReport { report, .. } = request;
- assert_eq!(
- report.$name,
- Some(Box::new($value)),
- );
- }
- )*
-
- assert!(
- await!(stream.try_next())
- .expect("failed to await on the next element")
- .is_none(),
- "stream should be empty"
- );
- });
-
- Ok(())
- }
- }
-
- executor.run(async { $event }, 2).expect("failed to run input event");
- }};
- }
-
- #[test]
- fn media_event_report() {
- assert_reports_eq!(TestConsumer,
- media_button_event(true, false, true, false, TestConsumer),
- [
- {
- media_buttons: MediaButtonsReport {
- volume_up: true,
- volume_down: false,
- mic_mute: true,
- reset: false,
- }
- },
- ]
- );
- }
-
- #[test]
- fn key_event_report() {
- assert_reports_eq!(TestConsumer,
- key_event(40, Duration::from_millis(0), TestConsumer),
- [
- {
- keyboard: KeyboardReport {
- pressed_keys: vec![40]
- }
- },
- {
- keyboard: KeyboardReport {
- pressed_keys: vec![]
- }
- },
- ]
- );
- }
-
- #[test]
- fn text_event_report() {
- assert_reports_eq!(TestConsumer,
- text("A".to_string(), Duration::from_millis(0), TestConsumer),
- [
- {
- keyboard: KeyboardReport {
- pressed_keys: vec![225]
- }
- },
- {
- keyboard: KeyboardReport {
- pressed_keys: vec![4, 225]
- }
- },
- {
- keyboard: KeyboardReport {
- pressed_keys: vec![]
- }
- },
- ]
- );
- }
-
- #[test]
- fn tap_event_report() {
- assert_reports_eq!(TestConsumer,
- tap_event(10, 10, 1000, 1000, 1, Duration::from_millis(0), TestConsumer),
- [
- {
- touchscreen: TouchscreenReport {
- touches: vec![Touch {
- finger_id: 1,
- x: 10,
- y: 10,
- width: 0,
- height: 0,
- }],
- }
- },
- {
- touchscreen: TouchscreenReport {
- touches: vec![],
- }
- },
- ]
- );
- }
-
- #[test]
- fn swipe_event_report() {
- assert_reports_eq!(TestConsumer,
- swipe(10, 10, 100, 100, 1000, 1000, 2, Duration::from_millis(0), TestConsumer),
- [
- {
- touchscreen: TouchscreenReport {
- touches: vec![Touch {
- finger_id: 1,
- x: 10,
- y: 10,
- width: 0,
- height: 0,
- }],
- }
- },
- {
- touchscreen: TouchscreenReport {
- touches: vec![Touch {
- finger_id: 1,
- x: 55,
- y: 55,
- width: 0,
- height: 0,
- }],
- }
- },
- {
- touchscreen: TouchscreenReport {
- touches: vec![Touch {
- finger_id: 1,
- x: 100,
- y: 100,
- width: 0,
- height: 0,
- }],
- }
- },
- {
- touchscreen: TouchscreenReport {
- touches: vec![],
- }
- },
- ]
- );
- }
-}