Flutter Engine
The Flutter Engine
pointer_delegate.cc
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "pointer_delegate.h"
6
7#include <lib/trace/event.h>
8#include <zircon/status.h>
9#include <zircon/types.h>
10
11#include <limits>
12
13#include "flutter/fml/logging.h"
14#include "flutter/fml/trace_event.h"
15
16// TODO(fxbug.dev/87076): Add MouseSource tests.
17
19// For using TouchInteractionId as a map key.
20bool operator==(const fuchsia::ui::pointer::TouchInteractionId& a,
21 const fuchsia::ui::pointer::TouchInteractionId& b) {
22 return a.device_id == b.device_id && a.pointer_id == b.pointer_id &&
23 a.interaction_id == b.interaction_id;
24}
25} // namespace fuchsia::ui::pointer
26
27namespace flutter_runner {
28
29using fup_EventPhase = fuchsia::ui::pointer::EventPhase;
30using fup_MouseDeviceInfo = fuchsia::ui::pointer::MouseDeviceInfo;
31using fup_MouseEvent = fuchsia::ui::pointer::MouseEvent;
32using fup_TouchEvent = fuchsia::ui::pointer::TouchEvent;
33using fup_TouchIxnStatus = fuchsia::ui::pointer::TouchInteractionStatus;
34using fup_TouchResponse = fuchsia::ui::pointer::TouchResponse;
35using fup_TouchResponseType = fuchsia::ui::pointer::TouchResponseType;
36using fup_ViewParameters = fuchsia::ui::pointer::ViewParameters;
37
38namespace {
39void IssueTouchTraceEvent(const fup_TouchEvent& event) {
40 FML_DCHECK(event.has_trace_flow_id()) << "API guarantee";
41 TRACE_FLOW_END("input", "dispatch_event_to_client", event.trace_flow_id());
42}
43
44void IssueMouseTraceEvent(const fup_MouseEvent& event) {
45 FML_DCHECK(event.has_trace_flow_id()) << "API guarantee";
46 TRACE_FLOW_END("input", "dispatch_event_to_client", event.trace_flow_id());
47}
48
49bool HasValidatedTouchSample(const fup_TouchEvent& event) {
50 if (!event.has_pointer_sample()) {
51 return false;
52 }
53 FML_DCHECK(event.pointer_sample().has_interaction()) << "API guarantee";
54 FML_DCHECK(event.pointer_sample().has_phase()) << "API guarantee";
55 FML_DCHECK(event.pointer_sample().has_position_in_viewport())
56 << "API guarantee";
57 return true;
58}
59
60bool HasValidatedMouseSample(const fup_MouseEvent& event) {
61 if (!event.has_pointer_sample()) {
62 return false;
63 }
64 const auto& sample = event.pointer_sample();
65 FML_DCHECK(sample.has_device_id()) << "API guarantee";
66 FML_DCHECK(sample.has_position_in_viewport()) << "API guarantee";
67 FML_DCHECK(!sample.has_pressed_buttons() || !sample.pressed_buttons().empty())
68 << "API guarantee";
69
70 return true;
71}
72
73std::array<float, 2> ViewportToViewCoordinates(
74 std::array<float, 2> viewport_coordinates,
75 const std::array<float, 9>& viewport_to_view_transform) {
76 // The transform matrix is a FIDL array with matrix data in column-major
77 // order. For a matrix with data [a b c d e f g h i], and with the viewport
78 // coordinates expressed as homogeneous coordinates, the logical view
79 // coordinates are obtained with the following formula:
80 // |a d g| |x| |x'|
81 // |b e h| * |y| = |y'|
82 // |c f i| |1| |w'|
83 // which we then normalize based on the w component:
84 // if z' not zero: (x'/w', y'/w')
85 // else (x', y')
86 const auto& M = viewport_to_view_transform;
87 const float x = viewport_coordinates[0];
88 const float y = viewport_coordinates[1];
89 const float xp = M[0] * x + M[3] * y + M[6];
90 const float yp = M[1] * x + M[4] * y + M[7];
91 const float wp = M[2] * x + M[5] * y + M[8];
92 if (wp != 0) {
93 return {xp / wp, yp / wp};
94 } else {
95 return {xp, yp};
96 }
97}
98
99flutter::PointerData::Change GetChangeFromTouchEventPhase(
100 fup_EventPhase phase) {
101 switch (phase) {
104 case fup_EventPhase::CHANGE:
106 case fup_EventPhase::REMOVE:
108 case fup_EventPhase::CANCEL:
110 default:
112 }
113}
114
115std::array<float, 2> ClampToViewSpace(const float x,
116 const float y,
117 const fup_ViewParameters& p) {
118 const float min_x = p.view.min[0];
119 const float min_y = p.view.min[1];
120 const float max_x = p.view.max[0];
121 const float max_y = p.view.max[1];
122 if (min_x <= x && x < max_x && min_y <= y && y < max_y) {
123 return {x, y}; // No clamping to perform.
124 }
125
126 // View boundary is [min_x, max_x) x [min_y, max_y). Note that min is
127 // inclusive, but max is exclusive - so we subtract epsilon.
128 const float max_x_inclusive = max_x - std::numeric_limits<float>::epsilon();
129 const float max_y_inclusive = max_y - std::numeric_limits<float>::epsilon();
130 const float& clamped_x = std::clamp(x, min_x, max_x_inclusive);
131 const float& clamped_y = std::clamp(y, min_y, max_y_inclusive);
132 FML_LOG(INFO) << "Clamped (" << x << ", " << y << ") to (" << clamped_x
133 << ", " << clamped_y << ").";
134 return {clamped_x, clamped_y};
135}
136
138 bool any_button_down,
139 std::unordered_set<uint32_t>& mouse_down,
140 uint32_t id) {
141 if (!mouse_down.count(id) && !any_button_down) {
143 } else if (!mouse_down.count(id) && any_button_down) {
144 mouse_down.insert(id);
146 } else if (mouse_down.count(id) && any_button_down) {
148 } else if (mouse_down.count(id) && !any_button_down) {
149 mouse_down.erase(id);
151 }
152
155}
156
157// Flutter's PointerData.device field is 64 bits and is expected to be unique
158// for each pointer. We pack Fuchsia's device ID (hi) and pointer ID (lo) into
159// 64 bits to retain uniqueness across multiple touch devices.
160uint64_t PackFuchsiaDeviceIdAndPointerId(uint32_t fuchsia_device_id,
161 uint32_t fuchsia_pointer_id) {
162 return (((uint64_t)fuchsia_device_id) << 32) | fuchsia_pointer_id;
163}
164
165// It returns a "draft" because the coordinates are logical. Later, view pixel
166// ratio is applied to obtain physical coordinates.
167//
168// The flutter pointerdata state machine has extra phases, which this function
169// synthesizes on the fly. Hence the return data is a flutter pointerdata, and
170// optionally a second one.
171// For example: <ADD, DOWN>, <MOVE, nullopt>, <UP, REMOVE>.
172// TODO(fxbug.dev/87074): Let PointerDataPacketConverter synthesize events.
173//
174// Flutter gestures expect a gesture to start within the logical view space, and
175// is not tolerant of floating point drift. This function coerces just the DOWN
176// event's coordinate to start within the logical view.
177std::pair<flutter::PointerData, std::optional<flutter::PointerData>>
178CreateTouchDraft(const fup_TouchEvent& event,
179 const fup_ViewParameters& view_parameters) {
180 FML_DCHECK(HasValidatedTouchSample(event)) << "precondition";
181 const auto& sample = event.pointer_sample();
182 const auto& ixn = sample.interaction();
183
185 ptr.Clear();
186 ptr.time_stamp = event.timestamp() / 1000; // in microseconds
187 ptr.change = GetChangeFromTouchEventPhase(sample.phase());
189 // Load Fuchsia's pointer ID onto Flutter's |device| field, and not the
190 // |pointer_identifier| field. The latter is written by
191 // PointerDataPacketConverter, to track individual gesture interactions.
192 ptr.device = PackFuchsiaDeviceIdAndPointerId(ixn.device_id, ixn.pointer_id);
193 // View parameters can change mid-interaction; apply transform on the fly.
194 auto logical =
195 ViewportToViewCoordinates(sample.position_in_viewport(),
196 view_parameters.viewport_to_view_transform);
197 ptr.physical_x = logical[0]; // Not yet physical; adjusted in PlatformView.
198 ptr.physical_y = logical[1]; // Not yet physical; adjusted in PlatformView.
199
200 // Match Flutter pointer's state machine with synthesized events.
203 memcpy(&down, &ptr, sizeof(flutter::PointerData));
205 { // Ensure gesture recognition: DOWN starts in the logical view space.
206 auto [x, y] =
207 ClampToViewSpace(down.physical_x, down.physical_y, view_parameters);
208 down.physical_x = x;
209 down.physical_y = y;
210 }
211 return {std::move(ptr), std::move(down)};
214 memcpy(&up, &ptr, sizeof(flutter::PointerData));
216 return {std::move(up), std::move(ptr)};
217 } else {
218 return {std::move(ptr), std::nullopt};
219 }
220}
221
222// It returns a "draft" because the coordinates are logical. Later, view pixel
223// ratio is applied to obtain physical coordinates.
224//
225// Phase data is computed before this call; it involves state tracking based on
226// button-down state.
227//
228// Button data, if available, gets packed into the |buttons| field, in flutter
229// button order (kMousePrimaryButton, etc). The device-assigned button IDs are
230// provided in priority order in MouseEvent.device_info (at the start of channel
231// connection), and maps from device button ID (given in fup_MouseEvent) to
232// flutter button ID (flutter::PointerData).
233//
234// Scroll data, if available, gets packed into the |scroll_delta_x| or
235// |scroll_delta_y| fields, and the |signal_kind| field is set to kScroll.
236// The PointerDataPacketConverter reads this field to synthesize events to match
237// Flutter's expected pointer stream.
238// TODO(fxbug.dev/87073): PointerDataPacketConverter should synthesize a
239// discrete scroll event on kDown or kUp, to match engine expectations.
240//
241// Flutter gestures expect a gesture to start within the logical view space, and
242// is not tolerant of floating point drift. This function coerces just the DOWN
243// event's coordinate to start within the logical view.
244flutter::PointerData CreateMouseDraft(const fup_MouseEvent& event,
246 const fup_ViewParameters& view_parameters,
247 const fup_MouseDeviceInfo& device_info) {
248 FML_DCHECK(HasValidatedMouseSample(event)) << "precondition";
249 const auto& sample = event.pointer_sample();
250
252 ptr.Clear();
253 ptr.time_stamp = event.timestamp() / 1000; // in microseconds
254 ptr.change = phase;
256 ptr.device = sample.device_id();
257
258 // View parameters can change mid-interaction; apply transform on the fly.
259 auto logical =
260 ViewportToViewCoordinates(sample.position_in_viewport(),
261 view_parameters.viewport_to_view_transform);
262 ptr.physical_x = logical[0]; // Not yet physical; adjusted in PlatformView.
263 ptr.physical_y = logical[1]; // Not yet physical; adjusted in PlatformView.
264
265 // Ensure gesture recognition: DOWN starts in the logical view space.
267 auto [x, y] =
268 ClampToViewSpace(ptr.physical_x, ptr.physical_y, view_parameters);
269 ptr.physical_x = x;
270 ptr.physical_y = y;
271 }
272
273 if (sample.has_pressed_buttons()) {
274 int64_t flutter_buttons = 0;
275 const auto& pressed = sample.pressed_buttons();
276 for (size_t idx = 0; idx < pressed.size(); ++idx) {
277 const uint8_t button_id = pressed[idx];
278 FML_DCHECK(device_info.has_buttons()) << "API guarantee";
279 // Priority 0 maps to kPrimaryButton, and so on.
280 for (uint8_t prio = 0; prio < device_info.buttons().size(); ++prio) {
281 if (button_id == device_info.buttons()[prio]) {
282 flutter_buttons |= (1 << prio);
283 }
284 }
285 }
286 FML_DCHECK(flutter_buttons != 0);
287 ptr.buttons = flutter_buttons;
288 }
289
290 // Fuchsia previously only provided scroll data in "ticks", not physical
291 // pixels. On legacy platforms, since Flutter expects scroll data in physical
292 // pixels, to compensate for lack of guidance, we make up a "reasonable
293 // amount".
294 // TODO(fxbug.dev/103443): Remove the tick based scrolling after the
295 // transition.
296 const int kScrollOffsetMultiplier = 20;
297
298 double dy = 0;
299 double dx = 0;
300 bool is_scroll = false;
301
302 if (sample.has_scroll_v_physical_pixel()) {
303 dy = -sample.scroll_v_physical_pixel();
304 is_scroll = true;
305 } else if (sample.has_scroll_v()) {
306 dy = -sample.scroll_v() *
307 kScrollOffsetMultiplier; // logical amount, not yet physical; adjusted
308 // in Platform View.
309 is_scroll = true;
310 }
311
312 if (sample.has_scroll_h_physical_pixel()) {
313 dx = sample.scroll_h_physical_pixel();
314 is_scroll = true;
315 } else if (sample.has_scroll_h()) {
316 dx = sample.scroll_h() * kScrollOffsetMultiplier; // logical amount
317 is_scroll = true;
318 }
319
320 if (is_scroll) {
322 ptr.scroll_delta_y = dy;
323 ptr.scroll_delta_x = dx;
324 }
325
326 return ptr;
327}
328
329// Helper to insert one or two events into a vector buffer.
330void InsertIntoBuffer(
331 std::pair<flutter::PointerData, std::optional<flutter::PointerData>> events,
332 std::vector<flutter::PointerData>* buffer) {
334 buffer->emplace_back(std::move(events.first));
335 if (events.second.has_value()) {
336 buffer->emplace_back(std::move(events.second.value()));
337 }
338}
339} // namespace
340
342 fuchsia::ui::pointer::TouchSourceHandle touch_source,
343 fuchsia::ui::pointer::MouseSourceHandle mouse_source)
344 : touch_source_(touch_source.Bind()), mouse_source_(mouse_source.Bind()) {
345 if (touch_source_) {
346 touch_source_.set_error_handler([](zx_status_t status) {
347 FML_LOG(ERROR) << "TouchSource channel error: << "
348 << zx_status_get_string(status);
349 });
350 }
351 if (mouse_source_) {
352 mouse_source_.set_error_handler([](zx_status_t status) {
353 FML_LOG(ERROR) << "MouseSource channel error: << "
354 << zx_status_get_string(status);
355 });
356 }
357}
358// Core logic of this class.
359// Aim to keep state management in this function.
361 std::function<void(std::vector<flutter::PointerData>)> callback) {
362 FML_LOG(INFO) << "Flutter - PointerDelegate started.";
363 if (touch_responder_) {
364 FML_LOG(ERROR) << "PointerDelegate::WatchLoop() must be called once.";
365 return;
366 }
367
368 touch_responder_ = [this, callback](std::vector<fup_TouchEvent> events) {
369 TRACE_EVENT0("flutter", "PointerDelegate::TouchHandler");
370 FML_DCHECK(touch_responses_.empty()) << "precondition";
371 std::vector<flutter::PointerData> to_client;
372 for (const fup_TouchEvent& event : events) {
373 IssueTouchTraceEvent(event);
375 response; // Response per event, matched on event's index.
376 if (event.has_view_parameters()) {
377 touch_view_parameters_ = std::move(event.view_parameters());
378 }
379 if (HasValidatedTouchSample(event)) {
380 const auto& sample = event.pointer_sample();
381 const auto& ixn = sample.interaction();
382 if (sample.phase() == fup_EventPhase::ADD &&
383 !event.has_interaction_result()) {
384 touch_buffer_.emplace(ixn, std::vector<flutter::PointerData>());
385 }
386
387 FML_DCHECK(touch_view_parameters_.has_value()) << "API guarantee";
388 auto events = CreateTouchDraft(event, touch_view_parameters_.value());
389 if (touch_buffer_.count(ixn) > 0) {
390 InsertIntoBuffer(std::move(events), &touch_buffer_[ixn]);
391 } else {
392 InsertIntoBuffer(std::move(events), &to_client);
393 }
394 // For this simple client, always claim we want the gesture.
395 response.set_response_type(fup_TouchResponseType::YES);
396 }
397 if (event.has_interaction_result()) {
398 const auto& result = event.interaction_result();
399 const auto& ixn = result.interaction;
400 if (result.status == fup_TouchIxnStatus::GRANTED &&
401 touch_buffer_.count(ixn) > 0) {
402 FML_DCHECK(to_client.empty()) << "invariant";
403 to_client.insert(to_client.end(), touch_buffer_[ixn].begin(),
404 touch_buffer_[ixn].end());
405 }
406 touch_buffer_.erase(ixn); // Result seen, delete the buffer.
407 }
408 touch_responses_.push_back(std::move(response));
409 }
410 callback(std::move(to_client)); // Notify client of touch events, if any.
411
412 touch_source_->Watch(std::move(touch_responses_),
413 /*copy*/ touch_responder_);
414 touch_responses_.clear();
415 };
416
417 mouse_responder_ = [this, callback](std::vector<fup_MouseEvent> events) {
418 TRACE_EVENT0("flutter", "PointerDelegate::MouseHandler");
419 std::vector<flutter::PointerData> to_client;
420 for (fup_MouseEvent& event : events) {
421 IssueMouseTraceEvent(event);
422 if (event.has_device_info()) {
423 const auto& id = event.device_info().id();
424 mouse_device_info_[id] = std::move(*event.mutable_device_info());
425 }
426 if (event.has_view_parameters()) {
427 mouse_view_parameters_ = std::move(event.view_parameters());
428 }
429 if (HasValidatedMouseSample(event)) {
430 const auto& sample = event.pointer_sample();
431 const auto& id = sample.device_id();
432 const bool any_button_down = sample.has_pressed_buttons();
433 FML_DCHECK(mouse_view_parameters_.has_value()) << "API guarantee";
434 FML_DCHECK(mouse_device_info_.count(id) > 0) << "API guarantee";
435
436 const auto phase = ComputePhase(any_button_down, mouse_down_, id);
438 CreateMouseDraft(event, phase, mouse_view_parameters_.value(),
439 mouse_device_info_[id]);
440 to_client.emplace_back(std::move(data));
441 }
442 }
443 callback(std::move(to_client));
444 mouse_source_->Watch(/*copy*/ mouse_responder_);
445 };
446
447 // Start watching both channels.
448 touch_source_->Watch(std::move(touch_responses_), /*copy*/ touch_responder_);
449 touch_responses_.clear();
450 if (mouse_source_) {
451 mouse_source_->Watch(/*copy*/ mouse_responder_);
452 }
453}
454
455} // namespace flutter_runner
static unsigned clamp(SkFixed fx, int max)
void WatchLoop(std::function< void(std::vector< flutter::PointerData >)> callback)
PointerDelegate(fuchsia::ui::pointer::TouchSourceHandle touch_source, fuchsia::ui::pointer::MouseSourceHandle mouse_source)
static bool b
struct MyStruct a[10]
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
FlKeyEvent * event
GAsyncResult * result
#define FML_LOG(severity)
Definition: logging.h:82
#define FML_UNREACHABLE()
Definition: logging.h:109
#define FML_DCHECK(condition)
Definition: logging.h:103
Dart_NativeFunction function
Definition: fuchsia.cc:51
bool pressed
double y
double x
skia_private::AutoTArray< sk_sp< SkImageFilter > > filters TypedMatrix matrix TypedMatrix matrix SkScalar dx
Definition: SkRecords.h:208
fuchsia::ui::pointer::MouseEvent fup_MouseEvent
fuchsia::ui::pointer::TouchInteractionStatus fup_TouchIxnStatus
fuchsia::ui::pointer::MouseDeviceInfo fup_MouseDeviceInfo
fuchsia::ui::pointer::TouchResponse fup_TouchResponse
fuchsia::ui::pointer::ViewParameters fup_ViewParameters
fuchsia::ui::pointer::TouchEvent fup_TouchEvent
fuchsia::ui::pointer::TouchResponseType fup_TouchResponseType
fuchsia::ui::pointer::EventPhase fup_EventPhase
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
bool operator==(const fuchsia::ui::pointer::TouchInteractionId &a, const fuchsia::ui::pointer::TouchInteractionId &b)
static bool Bind(PassBindingsCacheMTL &pass, ShaderStage stage, size_t bind_index, const BufferView &view)
#define M(PROC, DITHER)
SignalKind signal_kind
Definition: pointer_data.h:74
std::shared_ptr< const fml::Mapping > data
Definition: texture_gles.cc:63
const uintptr_t id
#define ERROR(message)
Definition: elf_loader.cc:260
#define TRACE_EVENT0(category_group, name)
Definition: trace_event.h:131
#define TRACE_FLOW_END(category, name, id)
Definition: trace_event.h:196