blob: 115da677248247d69f3906066c85fdbcacf9fafc [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 "lib/ui/input/device_state.h"
#include <fuchsia/ui/input/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include "lib/fidl/cpp/clone.h"
#include "lib/fxl/logging.h"
#include "lib/fxl/time/time_delta.h"
#include "lib/fxl/time/time_point.h"
namespace {
int64_t InputEventTimestampNow() {
return fxl::TimePoint::Now().ToEpochDelta().ToNanoseconds();
}
} // namespace
namespace mozart {
constexpr zx::duration kKeyRepeatSlow = zx::msec(250);
constexpr zx::duration kKeyRepeatFast = zx::msec(75);
KeyboardState::KeyboardState(DeviceState* device_state)
: device_state_(device_state),
keymap_(qwerty_map),
weak_ptr_factory_(this) {
char* keys = getenv("gfxconsole.keymap");
if (keys && !strcmp(keys, "dvorak")) {
keymap_ = dvorak_map;
}
}
void KeyboardState::SendEvent(fuchsia::ui::input::KeyboardEventPhase phase,
uint32_t key, uint64_t modifiers,
uint64_t timestamp) {
fuchsia::ui::input::InputEvent ev;
fuchsia::ui::input::KeyboardEvent kb;
kb.phase = phase;
kb.event_time = timestamp;
kb.device_id = device_state_->device_id();
kb.hid_usage = key;
kb.code_point =
hid_map_key(key,
modifiers & (fuchsia::ui::input::kModifierShift |
fuchsia::ui::input::kModifierCapsLock),
keymap_);
kb.modifiers = modifiers;
ev.set_keyboard(std::move(kb));
device_state_->callback()(std::move(ev));
}
void KeyboardState::Update(fuchsia::ui::input::InputReport input_report) {
FXL_DCHECK(input_report.keyboard);
uint64_t now = input_report.event_time;
std::vector<uint32_t> old_keys = keys_;
keys_.clear();
repeat_keys_.clear();
for (uint32_t key : input_report.keyboard->pressed_keys) {
keys_.push_back(key);
auto it = std::find(old_keys.begin(), old_keys.end(), key);
if (it != old_keys.end()) {
old_keys.erase(it);
continue;
}
SendEvent(fuchsia::ui::input::KeyboardEventPhase::PRESSED, key, modifiers_,
now);
uint64_t modifiers = modifiers_;
switch (key) {
case HID_USAGE_KEY_LEFT_SHIFT:
modifiers_ |= fuchsia::ui::input::kModifierLeftShift;
break;
case HID_USAGE_KEY_RIGHT_SHIFT:
modifiers_ |= fuchsia::ui::input::kModifierRightShift;
break;
case HID_USAGE_KEY_LEFT_CTRL:
modifiers_ |= fuchsia::ui::input::kModifierLeftControl;
break;
case HID_USAGE_KEY_RIGHT_CTRL:
modifiers_ |= fuchsia::ui::input::kModifierRightControl;
break;
case HID_USAGE_KEY_LEFT_ALT:
modifiers_ |= fuchsia::ui::input::kModifierLeftAlt;
break;
case HID_USAGE_KEY_RIGHT_ALT:
modifiers_ |= fuchsia::ui::input::kModifierRightAlt;
break;
case HID_USAGE_KEY_LEFT_GUI:
modifiers_ |= fuchsia::ui::input::kModifierLeftSuper;
break;
case HID_USAGE_KEY_RIGHT_GUI:
modifiers_ |= fuchsia::ui::input::kModifierRightSuper;
break;
default:
break;
}
// Don't repeat modifier by themselves
if (modifiers == modifiers_) {
repeat_keys_.push_back(key);
}
}
// If any key was released as well, do not repeat
if (!old_keys.empty()) {
repeat_keys_.clear();
}
for (uint32_t key : old_keys) {
SendEvent(fuchsia::ui::input::KeyboardEventPhase::RELEASED, key, modifiers_,
now);
switch (key) {
case HID_USAGE_KEY_LEFT_SHIFT:
modifiers_ &= (~fuchsia::ui::input::kModifierLeftShift);
break;
case HID_USAGE_KEY_RIGHT_SHIFT:
modifiers_ &= (~fuchsia::ui::input::kModifierRightShift);
break;
case HID_USAGE_KEY_LEFT_CTRL:
modifiers_ &= (~fuchsia::ui::input::kModifierLeftControl);
break;
case HID_USAGE_KEY_RIGHT_CTRL:
modifiers_ &= (~fuchsia::ui::input::kModifierRightControl);
break;
case HID_USAGE_KEY_LEFT_ALT:
modifiers_ &= (~fuchsia::ui::input::kModifierLeftAlt);
break;
case HID_USAGE_KEY_RIGHT_ALT:
modifiers_ &= (~fuchsia::ui::input::kModifierRightAlt);
break;
case HID_USAGE_KEY_LEFT_GUI:
modifiers_ &= (~fuchsia::ui::input::kModifierLeftSuper);
break;
case HID_USAGE_KEY_RIGHT_GUI:
modifiers_ &= (~fuchsia::ui::input::kModifierRightSuper);
break;
case HID_USAGE_KEY_CAPSLOCK:
if (modifiers_ & fuchsia::ui::input::kModifierCapsLock) {
modifiers_ &= (~fuchsia::ui::input::kModifierCapsLock);
} else {
modifiers_ |= fuchsia::ui::input::kModifierCapsLock;
}
break;
default:
break;
}
}
if (!repeat_keys_.empty()) {
ScheduleRepeat(++repeat_sequence_, kKeyRepeatSlow);
} else {
++repeat_sequence_;
}
}
void KeyboardState::Repeat(uint64_t sequence) {
if (sequence != repeat_sequence_) {
return;
}
uint64_t now = InputEventTimestampNow();
for (uint32_t key : repeat_keys_) {
SendEvent(fuchsia::ui::input::KeyboardEventPhase::REPEAT, key, modifiers_,
now);
}
ScheduleRepeat(sequence, kKeyRepeatFast);
}
void KeyboardState::ScheduleRepeat(uint64_t sequence, zx::duration delta) {
async::PostDelayedTask(
async_get_default_dispatcher(),
[weak = weak_ptr_factory_.GetWeakPtr(), sequence] {
if (weak)
weak->Repeat(sequence);
},
delta);
}
void MouseState::OnRegistered() {}
void MouseState::OnUnregistered() {}
void MouseState::SendEvent(float rel_x, float rel_y, int64_t timestamp,
fuchsia::ui::input::PointerEventPhase phase,
uint32_t buttons) {
fuchsia::ui::input::InputEvent ev;
fuchsia::ui::input::PointerEvent pt;
pt.event_time = timestamp;
pt.device_id = device_state_->device_id();
pt.pointer_id = device_state_->device_id();
pt.phase = phase;
pt.buttons = buttons;
pt.type = fuchsia::ui::input::PointerEventType::MOUSE;
pt.x = rel_x;
pt.y = rel_y;
ev.set_pointer(std::move(pt));
device_state_->callback()(std::move(ev));
}
void MouseState::Update(fuchsia::ui::input::InputReport input_report,
fuchsia::math::Size display_size) {
FXL_DCHECK(input_report.mouse);
uint64_t now = input_report.event_time;
uint8_t pressed = (input_report.mouse->pressed_buttons ^ buttons_) &
input_report.mouse->pressed_buttons;
uint8_t released =
(input_report.mouse->pressed_buttons ^ buttons_) & buttons_;
buttons_ = input_report.mouse->pressed_buttons;
// TODO(jpoichet) Update once we have an API to capture mouse.
// TODO(MZ-385): Quantize the mouse value to the range [0, display_width -
// mouse_resolution]
position_.x =
std::max(0.0f, std::min(position_.x + input_report.mouse->rel_x,
static_cast<float>(display_size.width)));
position_.y =
std::max(0.0f, std::min(position_.y + input_report.mouse->rel_y,
static_cast<float>(display_size.height)));
if (!pressed && !released) {
SendEvent(position_.x, position_.y, now,
fuchsia::ui::input::PointerEventPhase::MOVE, buttons_);
} else {
if (pressed) {
SendEvent(position_.x, position_.y, now,
fuchsia::ui::input::PointerEventPhase::DOWN, pressed);
}
if (released) {
SendEvent(position_.x, position_.y, now,
fuchsia::ui::input::PointerEventPhase::UP, released);
}
}
}
void StylusState::SendEvent(int64_t timestamp,
fuchsia::ui::input::PointerEventPhase phase,
fuchsia::ui::input::PointerEventType type, float x,
float y, uint32_t buttons) {
fuchsia::ui::input::PointerEvent pt;
pt.event_time = timestamp;
pt.device_id = device_state_->device_id();
pt.pointer_id = 1;
pt.type = type;
pt.phase = phase;
pt.x = x;
pt.y = y;
pt.buttons = buttons;
stylus_ = pt;
fuchsia::ui::input::InputEvent ev;
ev.set_pointer(std::move(pt));
device_state_->callback()(std::move(ev));
}
void StylusState::Update(fuchsia::ui::input::InputReport input_report,
fuchsia::math::Size display_size) {
FXL_DCHECK(input_report.stylus);
fuchsia::ui::input::StylusDescriptor* descriptor =
device_state_->stylus_descriptor();
FXL_DCHECK(descriptor);
const bool previous_stylus_down = stylus_down_;
const bool previous_stylus_in_range = stylus_in_range_;
stylus_down_ = input_report.stylus->is_in_contact;
stylus_in_range_ = input_report.stylus->in_range;
fuchsia::ui::input::PointerEventPhase phase =
fuchsia::ui::input::PointerEventPhase::DOWN;
if (stylus_down_) {
if (previous_stylus_down) {
phase = fuchsia::ui::input::PointerEventPhase::MOVE;
}
} else {
if (previous_stylus_down) {
phase = fuchsia::ui::input::PointerEventPhase::UP;
} else {
if (stylus_in_range_ && !previous_stylus_in_range) {
inverted_stylus_ = input_report.stylus->is_inverted;
phase = fuchsia::ui::input::PointerEventPhase::ADD;
} else if (!stylus_in_range_ && previous_stylus_in_range) {
phase = fuchsia::ui::input::PointerEventPhase::REMOVE;
} else if (stylus_in_range_) {
phase = fuchsia::ui::input::PointerEventPhase::HOVER;
} else {
return;
}
}
}
uint64_t now = input_report.event_time;
if (phase == fuchsia::ui::input::PointerEventPhase::UP) {
SendEvent(now, phase,
inverted_stylus_
? fuchsia::ui::input::PointerEventType::INVERTED_STYLUS
: fuchsia::ui::input::PointerEventType::STYLUS,
stylus_.x, stylus_.y, stylus_.buttons);
} else {
// Quantize the value to [0, 1) based on the resolution.
float x_denominator =
(1 +
static_cast<float>(descriptor->x.range.max - descriptor->x.range.min) /
static_cast<float>(descriptor->x.resolution)) *
static_cast<float>(descriptor->x.resolution);
float x =
static_cast<float>(display_size.width *
(input_report.stylus->x - descriptor->x.range.min)) /
x_denominator;
float y_denominator =
(1 +
static_cast<float>(descriptor->y.range.max - descriptor->y.range.min) /
static_cast<float>(descriptor->y.resolution)) *
static_cast<float>(descriptor->y.resolution);
float y =
static_cast<float>(display_size.height *
(input_report.stylus->y - descriptor->y.range.min)) /
y_denominator;
uint32_t buttons = 0;
if (input_report.stylus->pressed_buttons &
fuchsia::ui::input::kStylusBarrel) {
buttons |= fuchsia::ui::input::kStylusPrimaryButton;
}
SendEvent(now, phase,
inverted_stylus_
? fuchsia::ui::input::PointerEventType::INVERTED_STYLUS
: fuchsia::ui::input::PointerEventType::STYLUS,
x, y, buttons);
}
}
void TouchscreenState::Update(fuchsia::ui::input::InputReport input_report,
fuchsia::math::Size display_size) {
FXL_DCHECK(input_report.touchscreen);
fuchsia::ui::input::TouchscreenDescriptor* descriptor =
device_state_->touchscreen_descriptor();
FXL_DCHECK(descriptor);
std::vector<fuchsia::ui::input::PointerEvent> old_pointers = pointers_;
pointers_.clear();
uint64_t now = input_report.event_time;
for (auto& touch : input_report.touchscreen->touches) {
fuchsia::ui::input::InputEvent ev;
fuchsia::ui::input::PointerEvent pt;
pt.event_time = now;
pt.device_id = device_state_->device_id();
pt.phase = fuchsia::ui::input::PointerEventPhase::DOWN;
for (auto it = old_pointers.begin(); it != old_pointers.end(); ++it) {
FXL_DCHECK(touch.finger_id >= 0);
if (it->pointer_id == static_cast<uint32_t>(touch.finger_id)) {
pt.phase = fuchsia::ui::input::PointerEventPhase::MOVE;
old_pointers.erase(it);
break;
}
}
pt.pointer_id = touch.finger_id;
pt.type = fuchsia::ui::input::PointerEventType::TOUCH;
// Quantize the value to [0, 1) based on the resolution.
float x_denominator =
(1 +
static_cast<float>(descriptor->x.range.max - descriptor->x.range.min) /
static_cast<float>(descriptor->x.resolution)) *
static_cast<float>(descriptor->x.resolution);
float x = static_cast<float>(display_size.width *
(touch.x - descriptor->x.range.min)) /
x_denominator;
float y_denominator =
(1 +
static_cast<float>(descriptor->y.range.max - descriptor->y.range.min) /
static_cast<float>(descriptor->y.resolution)) *
static_cast<float>(descriptor->y.resolution);
float y = static_cast<float>(display_size.height *
(touch.y - descriptor->y.range.min)) /
y_denominator;
uint32_t width = 2 * touch.width;
uint32_t height = 2 * touch.height;
pt.x = x;
pt.y = y;
pt.radius_major = width > height ? width : height;
pt.radius_minor = width > height ? height : width;
pointers_.push_back(pt);
// For now when we get DOWN we need to fake trigger ADD first.
if (pt.phase == fuchsia::ui::input::PointerEventPhase::DOWN) {
fuchsia::ui::input::InputEvent add_ev = fidl::Clone(ev);
fuchsia::ui::input::PointerEvent add_pt = fidl::Clone(pt);
add_pt.phase = fuchsia::ui::input::PointerEventPhase::ADD;
add_ev.set_pointer(std::move(add_pt));
device_state_->callback()(std::move(add_ev));
}
ev.set_pointer(std::move(pt));
device_state_->callback()(std::move(ev));
}
for (const auto& pointer : old_pointers) {
fuchsia::ui::input::InputEvent ev;
fuchsia::ui::input::PointerEvent pt = fidl::Clone(pointer);
pt.phase = fuchsia::ui::input::PointerEventPhase::UP;
pt.event_time = now;
ev.set_pointer(std::move(pt));
device_state_->callback()(std::move(ev));
ev = fuchsia::ui::input::InputEvent();
pt = fidl::Clone(pointer);
pt.phase = fuchsia::ui::input::PointerEventPhase::REMOVE;
pt.event_time = now;
ev.set_pointer(std::move(pt));
device_state_->callback()(std::move(ev));
}
}
void SensorState::Update(fuchsia::ui::input::InputReport input_report) {
FXL_DCHECK(input_report.sensor);
FXL_DCHECK(device_state_->sensor_descriptor());
// Every sensor report gets routed via unique device_id.
device_state_->sensor_callback()(device_state_->device_id(),
std::move(input_report));
}
DeviceState::DeviceState(uint32_t device_id,
fuchsia::ui::input::DeviceDescriptor* descriptor,
OnEventCallback callback)
: device_id_(device_id),
descriptor_(descriptor),
keyboard_(this),
mouse_(this),
stylus_(this),
touchscreen_(this),
callback_(std::move(callback)),
sensor_(this),
sensor_callback_(nullptr) {}
DeviceState::DeviceState(uint32_t device_id,
fuchsia::ui::input::DeviceDescriptor* descriptor,
OnSensorEventCallback callback)
: device_id_(device_id),
descriptor_(descriptor),
keyboard_(this),
mouse_(this),
stylus_(this),
touchscreen_(this),
callback_(nullptr),
sensor_(this),
sensor_callback_(std::move(callback)) {}
DeviceState::~DeviceState() {}
void DeviceState::OnRegistered() {
if (descriptor_->keyboard) {
keyboard_.OnRegistered();
}
if (descriptor_->mouse) {
mouse_.OnRegistered();
}
if (descriptor_->stylus) {
stylus_.OnRegistered();
}
if (descriptor_->touchscreen) {
touchscreen_.OnRegistered();
}
if (descriptor_->sensor) {
sensor_.OnRegistered();
}
}
void DeviceState::OnUnregistered() {
if (descriptor_->keyboard) {
keyboard_.OnUnregistered();
}
if (descriptor_->mouse) {
mouse_.OnUnregistered();
}
if (descriptor_->stylus) {
stylus_.OnUnregistered();
}
if (descriptor_->touchscreen) {
touchscreen_.OnUnregistered();
}
if (descriptor_->sensor) {
sensor_.OnUnregistered();
}
}
void DeviceState::Update(fuchsia::ui::input::InputReport input_report,
fuchsia::math::Size display_size) {
if (input_report.keyboard && descriptor_->keyboard) {
keyboard_.Update(std::move(input_report));
} else if (input_report.mouse && descriptor_->mouse) {
mouse_.Update(std::move(input_report), display_size);
} else if (input_report.stylus && descriptor_->stylus) {
stylus_.Update(std::move(input_report), display_size);
} else if (input_report.touchscreen && descriptor_->touchscreen) {
touchscreen_.Update(std::move(input_report), display_size);
} else if (input_report.sensor && descriptor_->sensor) {
sensor_.Update(std::move(input_report));
}
}
} // namespace mozart