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![],
-                    }
-                },
-            ]
-        );
-    }
-}