#include "ui/events/fuchsia/pointer_events_handler.h"
#include <lib/async/default.h>
#include <cmath>
#include <limits>
#include <memory>
#include "base/fuchsia/fuchsia_logging.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "ui/events/event.h"
#include "ui/events/pointer_details.h"
#include "ui/events/types/event_type.h"
namespace fuchsia_ui_pointer {
bool operator==(const TouchInteractionId& a, const TouchInteractionId& b) {
return a.device_id() == b.device_id() && a.pointer_id() == b.pointer_id() &&
a.interaction_id() == b.interaction_id();
}
}
namespace ui {
namespace {
const int kWheelDelta = 120;
void IssueTouchTraceEvent(const fuchsia_ui_pointer::TouchEvent& event) {
DCHECK(event.trace_flow_id()) << "API guarantee";
TRACE_EVENT_WITH_FLOW0("input", "dispatch_event_to_client",
event.trace_flow_id().value(),
TRACE_EVENT_FLAG_FLOW_OUT);
}
void IssueMouseTraceEvent(const fuchsia_ui_pointer::MouseEvent& event) {
DCHECK(event.trace_flow_id()) << "API guarantee";
TRACE_EVENT_WITH_FLOW0("input", "dispatch_event_to_client",
event.trace_flow_id().value(),
TRACE_EVENT_FLAG_FLOW_OUT);
}
bool HasValidTouchSample(const fuchsia_ui_pointer::TouchEvent& event) {
if (!event.pointer_sample()) {
return false;
}
DCHECK(event.pointer_sample()->interaction()) << "API guarantee";
DCHECK(event.pointer_sample()->phase()) << "API guarantee";
DCHECK(event.pointer_sample()->position_in_viewport()) << "API guarantee";
return true;
}
bool HasValidMouseSample(const fuchsia_ui_pointer::MouseEvent& event) {
if (!event.pointer_sample()) {
return false;
}
const auto& sample = event.pointer_sample();
DCHECK(sample->device_id()) << "API guarantee";
DCHECK(sample->position_in_viewport()) << "API guarantee";
DCHECK(!sample->pressed_buttons() || sample->pressed_buttons()->size() > 0)
<< "API guarantee";
return true;
}
inline int FuchsiaButtonVectorToChromeButtonBitmap(
const std::vector<uint8_t>& pressed_buttons,
const std::vector<uint8_t>& possible_buttons) {
int result = 0;
const size_t num_buttons = possible_buttons.size();
for (auto button : pressed_buttons) {
if (num_buttons > 0 && button == possible_buttons[0]) {
result |= EF_LEFT_MOUSE_BUTTON;
}
if (num_buttons > 1 && button == possible_buttons[1]) {
result |= EF_RIGHT_MOUSE_BUTTON;
}
if (num_buttons > 2 && button == possible_buttons[2]) {
result |= EF_MIDDLE_MOUSE_BUTTON;
}
}
return result;
}
std::array<float, 2> ViewportToViewCoordinates(
std::array<float, 2> viewport_coordinates,
const std::array<float, 9>& viewport_to_view_transform) {
const auto& M = viewport_to_view_transform;
const float x = viewport_coordinates[0];
const float y = viewport_coordinates[1];
const float xp = M[0] * x + M[3] * y + M[6];
const float yp = M[1] * x + M[4] * y + M[7];
const float wp = M[2] * x + M[5] * y + M[8];
if (wp != 0) {
return {xp / wp, yp / wp};
} else {
return {xp, yp};
}
}
EventType GetEventTypeFromTouchEventPhase(
fuchsia_ui_pointer::EventPhase phase) {
switch (phase) {
case fuchsia_ui_pointer::EventPhase::kAdd:
return EventType::kTouchPressed;
case fuchsia_ui_pointer::EventPhase::kChange:
return EventType::kTouchMoved;
case fuchsia_ui_pointer::EventPhase::kRemove:
return EventType::kTouchReleased;
case fuchsia_ui_pointer::EventPhase::kCancel:
return EventType::kTouchCancelled;
}
}
std::array<float, 2> ClampToViewSpace(
const float x,
const float y,
const fuchsia_ui_pointer::ViewParameters& p) {
const float min_x = p.view().min()[0];
const float min_y = p.view().min()[1];
const float max_x = p.view().max()[0];
const float max_y = p.view().max()[1];
if (min_x <= x && x < max_x && min_y <= y && y < max_y) {
return {x, y};
}
const float max_x_inclusive = std::nextafter(max_x, min_x);
const float max_y_inclusive = std::nextafter(max_y, min_y);
const float clamped_x = std::ranges::clamp(x, min_x, max_x_inclusive);
const float clamped_y = std::ranges::clamp(y, min_y, max_y_inclusive);
return {clamped_x, clamped_y};
}
TouchEvent CreateTouchEventDraft(
const fuchsia_ui_pointer::TouchEvent& event,
const fuchsia_ui_pointer::ViewParameters& view_parameters) {
DCHECK(HasValidTouchSample(event)) << "precondition";
const auto& sample = event.pointer_sample();
const auto& interaction = sample->interaction();
auto timestamp = base::TimeTicks::FromZxTime(event.timestamp().value());
auto event_type = GetEventTypeFromTouchEventPhase(sample->phase().value());
DCHECK_LE(interaction->pointer_id(), 31U);
PointerDetails pointer_details(EventPointerType::kTouch,
interaction->pointer_id());
auto logical =
ViewportToViewCoordinates(sample->position_in_viewport().value(),
view_parameters.viewport_to_view_transform());
gfx::PointF location(logical[0], logical[1]);
gfx::PointF root_location(sample->position_in_viewport().value()[0],
sample->position_in_viewport().value()[1]);
return TouchEvent(event_type, location, root_location, timestamp,
pointer_details);
}
std::unique_ptr<MouseEvent> CreateMouseEventDraft(
const fuchsia_ui_pointer::MouseEvent& event,
const EventType event_type,
const int pressed_buttons_flags,
const int changed_buttons_flags,
const fuchsia_ui_pointer::ViewParameters& view_parameters,
const fuchsia_ui_pointer::MouseDeviceInfo& device_info) {
DCHECK(HasValidMouseSample(event)) << "precondition";
const auto& sample = event.pointer_sample();
auto timestamp = base::TimeTicks::FromZxTime(event.timestamp().value());
PointerDetails pointer_details(EventPointerType::kMouse,
sample->device_id().value());
auto logical =
ViewportToViewCoordinates(sample->position_in_viewport().value(),
view_parameters.viewport_to_view_transform());
if (event_type == EventType::kMousePressed) {
logical = ClampToViewSpace(logical[0], logical[1], view_parameters);
}
auto location = gfx::PointF(logical[0], logical[1]);
auto root_location = gfx::PointF(sample->position_in_viewport().value()[0],
sample->position_in_viewport().value()[1]);
if (event_type == EventType::kMousewheel) {
const int tick_x_120ths = sample->scroll_h().value_or(0) * kWheelDelta;
const int tick_y_120ths = sample->scroll_v().value_or(0) * kWheelDelta;
const float offset_x =
sample->scroll_h_physical_pixel().value_or(tick_x_120ths);
const float offset_y =
sample->scroll_v_physical_pixel().value_or(tick_y_120ths);
if (sample->is_precision_scroll().has_value() &&
sample->is_precision_scroll().value()) {
return std::make_unique<ScrollEvent>(
ui::EventType::kScroll, location, root_location, timestamp,
pressed_buttons_flags, offset_x, offset_y, offset_x, offset_y,
2);
}
return std::make_unique<MouseWheelEvent>(
gfx::Vector2d(static_cast<int>(offset_x), static_cast<int>(offset_y)),
location, root_location, timestamp, pressed_buttons_flags,
changed_buttons_flags, gfx::Vector2d(tick_x_120ths, tick_y_120ths));
}
auto mouse_event = std::make_unique<MouseEvent>(
event_type, location, root_location, timestamp, pressed_buttons_flags,
changed_buttons_flags, pointer_details);
mouse_event->InitializeNative();
return mouse_event;
}
}
PointerEventsHandler::PointerEventsHandler(
fidl::ClientEnd<fuchsia_ui_pointer::TouchSource> touch_source,
fidl::ClientEnd<fuchsia_ui_pointer::MouseSource> mouse_source)
: touch_source_(std::move(touch_source), async_get_default_dispatcher()),
mouse_source_(std::move(mouse_source), async_get_default_dispatcher()) {}
PointerEventsHandler::~PointerEventsHandler() = default;
void PointerEventsHandler::StartWatching(
base::RepeatingCallback<void(Event*)> event_callback) {
if (event_callback_) {
LOG(ERROR) << "PointerEventsHandler::StartWatching() must be called once.";
return;
}
event_callback_ = event_callback;
touch_source_->Watch(std::vector<fuchsia_ui_pointer::TouchResponse>())
.Then(fit::bind_member(this,
&PointerEventsHandler::OnTouchSourceWatchResult));
mouse_source_->Watch().Then(
fit::bind_member(this, &PointerEventsHandler::OnMouseSourceWatchResult));
}
void PointerEventsHandler::OnTouchSourceWatchResult(
fidl::Result<fuchsia_ui_pointer::TouchSource::Watch>& watch_result) {
if (watch_result.is_error()) {
ZX_DLOG(ERROR, watch_result.error_value().status()) << " in " << __func__;
return;
}
auto& events = watch_result->events();
TRACE_EVENT0("input", "PointerEventsHandler::OnTouchSourceWatchResult");
std::vector<fuchsia_ui_pointer::TouchResponse> touch_responses;
for (const fuchsia_ui_pointer::TouchEvent& event : events) {
IssueTouchTraceEvent(event);
fuchsia_ui_pointer::TouchResponse
response;
if (event.view_parameters()) {
touch_view_parameters_ = std::move(event.view_parameters().value());
}
if (HasValidTouchSample(event)) {
const auto& sample = event.pointer_sample();
const auto& interaction = sample->interaction().value();
if (sample->phase().value() == fuchsia_ui_pointer::EventPhase::kAdd &&
!event.interaction_result()) {
touch_buffer_.emplace(interaction, std::vector<TouchEvent>());
}
DCHECK(touch_view_parameters_.has_value()) << "API guarantee";
auto draft = CreateTouchEventDraft(event, touch_view_parameters_.value());
if (touch_buffer_.count(interaction) > 0) {
touch_buffer_[interaction].emplace_back(std::move(draft));
} else {
event_callback_.Run(&draft);
}
response.response_type(fuchsia_ui_pointer::TouchResponseType::kYes);
}
if (event.interaction_result()) {
const auto& result = event.interaction_result();
const auto& interaction = result->interaction();
if (result->status() ==
fuchsia_ui_pointer::TouchInteractionStatus::kGranted &&
touch_buffer_.count(interaction) > 0) {
for (auto& touch : touch_buffer_[interaction]) {
event_callback_.Run(&touch);
}
}
touch_buffer_.erase(interaction);
}
touch_responses.push_back(std::move(response));
}
touch_source_->Watch(std::move(touch_responses))
.Then(fit::bind_member(this,
&PointerEventsHandler::OnTouchSourceWatchResult));
}
void PointerEventsHandler::OnMouseSourceWatchResult(
fidl::Result<fuchsia_ui_pointer::MouseSource::Watch>& watch_result) {
if (watch_result.is_error()) {
DLOG(ERROR) << "OnMouseSourceWatchResult: "
<< watch_result.error_value().status_string();
return;
}
auto& events = watch_result->events();
TRACE_EVENT0("input", "PointerEventsHandler::OnMouseSourceWatchResult");
for (fuchsia_ui_pointer::MouseEvent& event : events) {
IssueMouseTraceEvent(event);
if (event.device_info()) {
const auto& id = event.device_info()->id().value();
mouse_device_info_[id] = std::move(event.device_info().value());
}
if (event.view_parameters()) {
mouse_view_parameters_ = std::move(event.view_parameters().value());
}
if (HasValidMouseSample(event)) {
const auto& sample = event.pointer_sample();
const auto& id = sample->device_id().value();
DCHECK(mouse_view_parameters_.has_value()) << "API guarantee";
DCHECK(mouse_device_info_.count(id) > 0) << "API guarantee";
int pressed_buttons = sample->pressed_buttons()
? FuchsiaButtonVectorToChromeButtonBitmap(
sample->pressed_buttons().value(),
mouse_device_info_[id].buttons().value())
: 0;
int previous_buttons = mouse_down_[id];
int changed_buttons = pressed_buttons ^ previous_buttons;
mouse_down_[id] = pressed_buttons;
const bool is_wheel_event = sample->scroll_v() || sample->scroll_h() ||
sample->scroll_h_physical_pixel() ||
sample->scroll_v_physical_pixel();
const bool is_button_event = changed_buttons != 0;
const bool is_move_or_drag_event = !is_wheel_event && !is_button_event;
if (is_button_event) {
for (int button = EF_LEFT_MOUSE_BUTTON; button <= EF_RIGHT_MOUSE_BUTTON;
button = button << 1) {
DCHECK(button == EF_LEFT_MOUSE_BUTTON ||
button == EF_MIDDLE_MOUSE_BUTTON ||
button == EF_RIGHT_MOUSE_BUTTON);
bool prev_down = previous_buttons & button;
bool curr_down = pressed_buttons & button;
if (!prev_down && !curr_down) {
continue;
} else if (!prev_down && curr_down) {
auto event_type = EventType::kMousePressed;
auto draft = CreateMouseEventDraft(
event, event_type, button, changed_buttons,
mouse_view_parameters_.value(), mouse_device_info_[id]);
event_callback_.Run(draft.get());
} else if (prev_down && !curr_down) {
auto event_type = EventType::kMouseReleased;
auto draft = CreateMouseEventDraft(
event, event_type, button, changed_buttons,
mouse_view_parameters_.value(), mouse_device_info_[id]);
event_callback_.Run(draft.get());
}
}
}
if (is_wheel_event) {
auto draft = CreateMouseEventDraft(
event, EventType::kMousewheel, pressed_buttons, changed_buttons,
mouse_view_parameters_.value(), mouse_device_info_[id]);
event_callback_.Run(draft.get());
}
if (is_move_or_drag_event) {
auto event_type = (pressed_buttons == 0) ? EventType::kMouseMoved
: EventType::kMouseDragged;
auto draft = CreateMouseEventDraft(
event, event_type, pressed_buttons, changed_buttons,
mouse_view_parameters_.value(), mouse_device_info_[id]);
event_callback_.Run(draft.get());
}
}
}
mouse_source_->Watch().Then(
fit::bind_member(this, &PointerEventsHandler::OnMouseSourceWatchResult));
}
}