blob: cfa984e9fabb5f367bf678a163ab3b1dd7417460 [file] [log] [blame]
// 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::TraceProvider trace_provider(loop.dispatcher(), "input");
loop.Run();
return 0;
}