Multi-view pointer event (flutter/engine#46213)

This PR adds a new field `view_id` to embedder API's `FlutterPointerEvent`, allowing platforms to specify the source view of pointer events.

https://github.com/flutter/flutter/issues/112205

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
Tong Mu
2023-12-20 11:21:22 -08:00
committed by GitHub
parent 20f78ab352
commit 2e551cb5c3
18 changed files with 181 additions and 18 deletions

View File

@@ -13,6 +13,7 @@
static double g_pixelRatio = 1.0;
static const size_t kInitialWindowWidth = 800;
static const size_t kInitialWindowHeight = 600;
static constexpr FlutterViewId kImplicitViewId = 0;
static_assert(FLUTTER_ENGINE_VERSION == 1,
"This Flutter Embedder was authored against the stable Flutter "
@@ -33,6 +34,9 @@ void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window,
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
// This example only supports a single window, therefore we assume the pointer
// event occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendPointerEvent(
reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)), &event,
1);

View File

@@ -26,6 +26,7 @@ static const size_t kInitialWindowHeight = 600;
// Maximum damage history - for triple buffering we need to store damage for
// last two frames; Some Android devices (Pixel 4) use quad buffering.
static const int kMaxHistorySize = 10;
static constexpr FlutterViewId kImplicitViewId = 0;
// Keeps track of the most recent frame damages so that existing damage can
// be easily computed.
@@ -56,6 +57,9 @@ void GLFWcursorPositionCallbackAtPhase(GLFWwindow* window,
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now().time_since_epoch())
.count();
// This example only supports a single window, therefore we assume the pointer
// event occurred in the only view, the implicit view.
event.view_id = kImplicitViewId;
FlutterEngineSendPointerEvent(
reinterpret_cast<FlutterEngine>(glfwGetWindowUserPointer(window)), &event,
1);

View File

@@ -414,12 +414,9 @@ class PlatformDispatcher {
}
}
// If this value changes, update the encoding code in the following files:
//
// * pointer_data.cc
// * pointer.dart
// * AndroidTouchProcessor.java
static const int _kPointerDataFieldCount = 35;
// This value must match kPointerDataFieldCount in pointer_data.cc. (The
// pointer_data.cc also lists other locations that must be kept consistent.)
static const int _kPointerDataFieldCount = 36;
static PointerDataPacket _unpackPointerDataPacket(ByteData packet) {
const int kStride = Int64List.bytesPerElement;
@@ -430,7 +427,7 @@ class PlatformDispatcher {
for (int i = 0; i < length; ++i) {
int offset = i * _kPointerDataFieldCount;
data.add(PointerData(
// TODO(goderbauer): Wire up viewId.
// The unpacking code must match the struct in pointer_data.h.
embedderId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
timeStamp: Duration(microseconds: packet.getInt64(kStride * offset++, _kFakeHostEndian)),
change: PointerChange.values[packet.getInt64(kStride * offset++, _kFakeHostEndian)],
@@ -466,6 +463,7 @@ class PlatformDispatcher {
panDeltaY: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
scale: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
rotation: packet.getFloat64(kStride * offset++, _kFakeHostEndian),
viewId: packet.getInt64(kStride * offset++, _kFakeHostEndian),
));
assert(offset == (i + 1) * _kPointerDataFieldCount);
}

View File

@@ -419,7 +419,8 @@ class PointerData {
'panDeltaX: $panDeltaX, '
'panDeltaY: $panDeltaY, '
'scale: $scale, '
'rotation: $rotation'
'rotation: $rotation, '
'viewId: $viewId'
')';
}
}

View File

@@ -8,6 +8,17 @@
namespace flutter {
// The number of fields of PointerData.
//
// If kPointerDataFieldCount changes, update the corresponding values to:
//
// * _kPointerDataFieldCount in platform_dispatcher.dart
// * POINTER_DATA_FIELD_COUNT in AndroidTouchProcessor.java
//
// (This is a centralized list of all locations that should be kept up-to-date.)
static constexpr int kPointerDataFieldCount = 36;
static constexpr int kBytesPerField = sizeof(int64_t);
static_assert(sizeof(PointerData) == kBytesPerField * kPointerDataFieldCount,
"PointerData has the wrong size");

View File

@@ -9,10 +9,6 @@
namespace flutter {
// If this value changes, update the pointer data unpacking code in
// platform_dispatcher.dart.
static constexpr int kPointerDataFieldCount = 35;
static constexpr int kBytesPerField = sizeof(int64_t);
// Must match the button constants in events.dart.
enum PointerButtonMouse : int64_t {
kPointerButtonMousePrimary = 1 << 0,
@@ -32,7 +28,13 @@ enum PointerButtonStylus : int64_t {
kPointerButtonStylusSecondary = 1 << 2,
};
// This structure is unpacked by hooks.dart.
// This structure is unpacked by platform_dispatcher.dart.
//
// If this struct changes, update:
// * kPointerDataFieldCount in pointer_data.cc. (The pointer_data.cc also
// lists out other locations that must be kept consistent.)
// * The functions to create simulated data in
// pointer_data_packet_converter_unittests.cc.
struct alignas(8) PointerData {
// Must match the PointerChange enum in pointer.dart.
enum class Change : int64_t {
@@ -100,6 +102,7 @@ struct alignas(8) PointerData {
double pan_delta_y;
double scale;
double rotation;
int64_t view_id;
void Clear();
};

View File

@@ -45,6 +45,7 @@ void CreateSimulatedPointerData(PointerData& data, // NOLINT
data.platformData = 0;
data.scroll_delta_x = 0.0;
data.scroll_delta_y = 0.0;
data.view_id = 0;
}
void CreateSimulatedMousePointerData(PointerData& data, // NOLINT
@@ -84,6 +85,7 @@ void CreateSimulatedMousePointerData(PointerData& data, // NOLINT
data.platformData = 0;
data.scroll_delta_x = scroll_delta_x;
data.scroll_delta_y = scroll_delta_y;
data.view_id = 0;
}
void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT
@@ -129,6 +131,7 @@ void CreateSimulatedTrackpadGestureData(PointerData& data, // NOLINT
data.pan_delta_y = 0.0;
data.scale = scale;
data.rotation = rotation;
data.view_id = 0;
}
void UnpackPointerPacket(std::vector<PointerData>& output, // NOLINT
@@ -713,5 +716,25 @@ TEST(PointerDataPacketConverterTest, CanConvertTrackpadGesture) {
ASSERT_EQ(result[3].synthesized, 0);
}
TEST(PointerDataPacketConverterTest, CanConvertViewId) {
PointerDataPacketConverter converter;
auto packet = std::make_unique<PointerDataPacket>(2);
PointerData data;
CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0, 0.0, 0.0, 0);
data.view_id = 100;
packet->SetPointerData(0, data);
CreateSimulatedPointerData(data, PointerData::Change::kHover, 0, 1.0, 0.0, 0);
data.view_id = 200;
packet->SetPointerData(1, data);
auto converted_packet = converter.Convert(std::move(packet));
std::vector<PointerData> result;
UnpackPointerPacket(result, std::move(converted_packet));
ASSERT_EQ(result.size(), (size_t)2);
ASSERT_EQ(result[0].view_id, 100);
ASSERT_EQ(result[1].view_id, 200);
}
} // namespace testing
} // namespace flutter

View File

@@ -147,7 +147,8 @@ class PointerData {
'panDeltaX: $panDeltaX, '
'panDeltaY: $panDeltaY, '
'scale: $scale, '
'rotation: $rotation'
'rotation: $rotation, '
'viewId: $viewId'
')';
}
}

View File

@@ -80,8 +80,9 @@ public class AndroidTouchProcessor {
int UNKNOWN = 4;
}
// Must match the unpacking code in hooks.dart.
private static final int POINTER_DATA_FIELD_COUNT = 35;
// This value must match kPointerDataFieldCount in pointer_data.cc. (The
// pointer_data.cc also lists other locations that must be kept consistent.)
private static final int POINTER_DATA_FIELD_COUNT = 36;
@VisibleForTesting static final int BYTES_PER_FIELD = 8;
// Default if context is null, chosen to ensure reasonable speed scrolling.
@@ -92,6 +93,9 @@ public class AndroidTouchProcessor {
// This flag indicates whether the original Android pointer events were batched together.
private static final int POINTER_DATA_FLAG_BATCHED = 1;
// The view ID for the only view in a single-view Flutter app.
private static final int IMPLICIT_VIEW_ID = 0;
@NonNull private final FlutterRenderer renderer;
@NonNull private final MotionEventTracker motionEventTracker;
@@ -134,6 +138,8 @@ public class AndroidTouchProcessor {
public boolean onTouchEvent(@NonNull MotionEvent event, @NonNull Matrix transformMatrix) {
int pointerCount = event.getPointerCount();
// The following packing code must match the struct in pointer_data.h.
// Prepare a data packet of the appropriate size and order.
ByteBuffer packet =
ByteBuffer.allocateDirect(pointerCount * POINTER_DATA_FIELD_COUNT * BYTES_PER_FIELD);
@@ -253,6 +259,10 @@ public class AndroidTouchProcessor {
if (pointerChange == -1) {
return;
}
// TODO(dkwingsmt): Use the correct source view ID once Android supports
// multiple views.
// https://github.com/flutter/flutter/issues/134405
final int viewId = IMPLICIT_VIEW_ID;
final int pointerId = event.getPointerId(pointerIndex);
int pointerKind = getPointerDeviceTypeForToolType(event.getToolType(pointerIndex));
@@ -411,6 +421,7 @@ public class AndroidTouchProcessor {
packet.putDouble(0.0); // pan_delta_y
packet.putDouble(1.0); // scale
packet.putDouble(0.0); // rotation
packet.putLong(viewId); // view_id
if (isTrackpadPan && (panZoomType == PointerChange.PAN_ZOOM_END)) {
ongoingPans.remove(pointerId);

View File

@@ -9,6 +9,7 @@
#import <os/log.h>
#include <memory>
#include "flutter/common/constants.h"
#include "flutter/fml/memory/weak_ptr.h"
#include "flutter/fml/message_loop.h"
#include "flutter/fml/platform/darwin/platform_version.h"
@@ -58,6 +59,11 @@ typedef struct MouseState {
// change. Unfortunately unless you have Werror turned on, incompatible pointers as arguments are
// just a warning.
@interface FlutterViewController () <FlutterBinaryMessenger, UIScrollViewDelegate>
// TODO(dkwingsmt): Make the view ID property public once the iOS shell
// supports multiple views.
// https://github.com/flutter/flutter/issues/138168
@property(nonatomic, readonly) int64_t viewIdentifier;
@property(nonatomic, readwrite, getter=isDisplayingFlutterUI) BOOL displayingFlutterUI;
@property(nonatomic, assign) BOOL isHomeIndicatorHidden;
@property(nonatomic, assign) BOOL isPresentingViewControllerAnimating;
@@ -148,6 +154,7 @@ typedef struct MouseState {
@synthesize displayingFlutterUI = _displayingFlutterUI;
@synthesize prefersStatusBarHidden = _flutterPrefersStatusBarHidden;
@dynamic viewIdentifier;
#pragma mark - Manage and override all designated initializers
@@ -642,6 +649,12 @@ static void SendFakeTouchEvent(UIScreen* screen,
#pragma mark - Properties
- (int64_t)viewIdentifier {
// TODO(dkwingsmt): Fill the view ID property with the correct value once the
// iOS shell supports multiple views.
return flutter::kFlutterImplicitViewId;
}
- (UIView*)splashScreenView {
if (!_splashScreenView) {
return nil;
@@ -928,6 +941,7 @@ static void SendFakeTouchEvent(UIScreen* screen,
pointer_data.change = flutter::PointerData::Change::kCancel;
pointer_data.device = device.longLongValue;
pointer_data.pointer_identifier = 0;
pointer_data.view_id = self.viewIdentifier;
// Anything we put here will be arbitrary since there are no touches.
pointer_data.physical_x = 0;
@@ -1176,6 +1190,8 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
pointer_data.device = reinterpret_cast<int64_t>(touch);
pointer_data.view_id = self.viewIdentifier;
// Pointer will be generated in pointer_data_packet_converter.cc.
pointer_data.pointer_identifier = 0;
@@ -2393,6 +2409,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
pointer_data.device = reinterpret_cast<int64_t>(_continuousScrollingPanGestureRecognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
pointer_data.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
pointer_data.view_id = self.viewIdentifier;
if (event.timestamp < _scrollInertiaEventAppKitDeadline) {
// Only send the event if it occured before the expected natural end of gesture momentum.
@@ -2416,6 +2433,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
flutter::PointerData pointer_data = [self generatePointerDataAtLastMouseLocation];
pointer_data.device = reinterpret_cast<int64_t>(recognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kMouse;
pointer_data.view_id = self.viewIdentifier;
switch (_hoverGestureRecognizer.state) {
case UIGestureRecognizerStateBegan:
@@ -2454,6 +2472,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
inertia_cancel.device = reinterpret_cast<int64_t>(_continuousScrollingPanGestureRecognizer);
inertia_cancel.kind = flutter::PointerData::DeviceKind::kTrackpad;
inertia_cancel.signal_kind = flutter::PointerData::SignalKind::kScrollInertiaCancel;
inertia_cancel.view_id = self.viewIdentifier;
packet->SetPointerData(/*i=*/1, inertia_cancel);
[_engine.get() dispatchPointerDataPacket:std::move(packet)];
_scrollInertiaEventStartline = DBL_MAX;
@@ -2477,6 +2496,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
pointer_data.signal_kind = flutter::PointerData::SignalKind::kScroll;
pointer_data.scroll_delta_x = (translation.x - _mouseState.last_translation.x);
pointer_data.scroll_delta_y = -(translation.y - _mouseState.last_translation.y);
pointer_data.view_id = self.viewIdentifier;
// The translation reported by UIPanGestureRecognizer is the total translation
// generated by the pan gesture since the gesture began. We need to be able
@@ -2500,6 +2520,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
flutter::PointerData pointer_data = [self generatePointerDataAtLastMouseLocation];
pointer_data.device = reinterpret_cast<int64_t>(recognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
pointer_data.view_id = self.viewIdentifier;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
pointer_data.change = flutter::PointerData::Change::kPanZoomStart;
@@ -2548,6 +2569,7 @@ static flutter::PointerData::DeviceKind DeviceKindFromTouchType(UITouch* touch)
flutter::PointerData pointer_data = [self generatePointerDataAtLastMouseLocation];
pointer_data.device = reinterpret_cast<int64_t>(recognizer);
pointer_data.kind = flutter::PointerData::DeviceKind::kTrackpad;
pointer_data.view_id = self.viewIdentifier;
switch (recognizer.state) {
case UIGestureRecognizerStateBegan:
pointer_data.change = flutter::PointerData::Change::kPanZoomStart;

View File

@@ -743,6 +743,7 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
.device_kind = deviceKind,
// If a click triggered a synthesized kAdd, don't pass the buttons in that event.
.buttons = phase == kAdd ? 0 : _mouseState.buttons,
.view_id = static_cast<FlutterViewId>(_viewId),
};
if (phase == kPanZoomUpdate) {
@@ -1049,6 +1050,7 @@ static void CommonInit(FlutterViewController* controller, FlutterEngine* engine)
.device = kPointerPanZoomDeviceId,
.signal_kind = kFlutterPointerSignalKindScrollInertiaCancel,
.device_kind = kFlutterPointerDeviceKindTrackpad,
.view_id = static_cast<FlutterViewId>(_viewId),
};
[_engine sendPointerEvent:flutterEvent];

View File

@@ -102,7 +102,7 @@ extern const intptr_t kPlatformStrongDillSize;
const int32_t kFlutterSemanticsNodeIdBatchEnd = -1;
const int32_t kFlutterSemanticsCustomActionIdBatchEnd = -1;
static constexpr int64_t kFlutterImplicitViewId = 0;
static constexpr FlutterViewId kFlutterImplicitViewId = 0;
// A message channel to send platform-independent FlutterKeyData to the
// framework.
@@ -2326,6 +2326,8 @@ FlutterEngineResult FlutterEngineSendPointerEvent(
pointer_data.pan_delta_y = 0.0;
pointer_data.scale = SAFE_ACCESS(current, scale, 0.0);
pointer_data.rotation = SAFE_ACCESS(current, rotation, 0.0);
pointer_data.view_id =
SAFE_ACCESS(current, view_id, kFlutterImplicitViewId);
packet->SetPointerData(i, pointer_data);
current = reinterpret_cast<const FlutterPointerEvent*>(
reinterpret_cast<const uint8_t*>(current) + current->struct_size);

View File

@@ -266,6 +266,12 @@ typedef enum {
typedef struct _FlutterEngine* FLUTTER_API_SYMBOL(FlutterEngine);
/// Unique identifier for views.
///
/// View IDs are generated by the embedder and are
/// opaque to the engine; the engine does not interpret view IDs in any way.
typedef int64_t FlutterViewId;
typedef struct {
/// horizontal scale factor
double scaleX;
@@ -961,6 +967,8 @@ typedef struct {
double scale;
/// The rotation of the pan/zoom in radians, where 0.0 is the initial angle.
double rotation;
/// The identifier of the view that received the pointer event.
FlutterViewId view_id;
} FlutterPointerEvent;
typedef enum {

View File

@@ -1308,7 +1308,7 @@ void pointer_data_packet() {
(PointerDataPacket packet) {
signalNativeCount(packet.data.length);
for (final pointerData in packet.data) {
for (final PointerData pointerData in packet.data) {
signalNativeMessage(pointerData.toString());
}
};
@@ -1316,6 +1316,19 @@ void pointer_data_packet() {
signalNativeTest();
}
@pragma('vm:entry-point')
void pointer_data_packet_view_id() {
PlatformDispatcher.instance.onPointerDataPacket = (PointerDataPacket packet) {
assert(packet.data.length == 1);
for (final PointerData pointerData in packet.data) {
signalNativeMessage('ViewID: ${pointerData.viewId}');
}
};
signalNativeTest();
}
@pragma('vm:entry-point')
Future<void> channel_listener_response() async {
channelBuffers.setListener('test/listen',

View File

@@ -2666,6 +2666,48 @@ TEST_F(EmbedderTest, CanSendPointer) {
message_latch.Wait();
}
/// Send a pointer event to Dart and wait until the Dart code echos with the
/// view ID.
TEST_F(EmbedderTest, CanSendPointerWithViewId) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);
EmbedderConfigBuilder builder(context);
builder.SetSoftwareRendererConfig();
builder.SetDartEntrypoint("pointer_data_packet_view_id");
fml::AutoResetWaitableEvent ready_latch, count_latch, message_latch;
context.AddNativeCallback(
"SignalNativeTest",
CREATE_NATIVE_ENTRY(
[&ready_latch](Dart_NativeArguments args) { ready_latch.Signal(); }));
context.AddNativeCallback(
"SignalNativeMessage",
CREATE_NATIVE_ENTRY([&message_latch](Dart_NativeArguments args) {
auto message = tonic::DartConverter<std::string>::FromDart(
Dart_GetNativeArgument(args, 0));
ASSERT_EQ("ViewID: 2", message);
message_latch.Signal();
}));
auto engine = builder.LaunchEngine();
ASSERT_TRUE(engine.is_valid());
ready_latch.Wait();
FlutterPointerEvent pointer_event = {};
pointer_event.struct_size = sizeof(FlutterPointerEvent);
pointer_event.phase = FlutterPointerPhase::kAdd;
pointer_event.x = 123;
pointer_event.y = 456;
pointer_event.timestamp = static_cast<size_t>(1234567890);
pointer_event.view_id = 2;
FlutterEngineResult result =
FlutterEngineSendPointerEvent(engine.get(), &pointer_event, 1);
ASSERT_EQ(result, kSuccess);
message_latch.Wait();
}
TEST_F(EmbedderTest, RegisterChannelListener) {
auto& context = GetEmbedderContext(EmbedderTestContextType::kSoftwareContext);

View File

@@ -14,6 +14,7 @@
#include <iostream>
#include <string>
#include "flutter/common/constants.h"
#include "flutter/shell/platform/common/client_wrapper/include/flutter/plugin_registrar.h"
#include "flutter/shell/platform/common/incoming_message_dispatcher.h"
#include "flutter/shell/platform/common/path_utils.h"
@@ -391,6 +392,9 @@ static void SendPointerEventWithData(GLFWwindow* window,
event.y *= pixels_per_coordinate;
event.scroll_delta_x *= pixels_per_coordinate;
event.scroll_delta_y *= pixels_per_coordinate;
// The GLFW embedder doesn't support multiple views. We assume all pointer
// events come from the only view, the implicit view.
event.view_id = flutter::kFlutterImplicitViewId;
FlutterEngineSendPointerEvent(controller->engine->flutter_engine, &event, 1);

View File

@@ -10,6 +10,7 @@
#include <string>
#include <vector>
#include "flutter/common/constants.h"
#include "flutter/shell/platform/common/app_lifecycle_state.h"
#include "flutter/shell/platform/common/engine_switches.h"
#include "flutter/shell/platform/embedder/embedder.h"
@@ -802,6 +803,10 @@ void fl_engine_send_mouse_pointer_event(FlEngine* self,
fl_event.device_kind = kFlutterPointerDeviceKindMouse;
fl_event.buttons = buttons;
fl_event.device = kMousePointerDeviceId;
// TODO(dkwingsmt): Assign the correct view ID once the Linux embedder
// supports multiple views.
// https://github.com/flutter/flutter/issues/138178
fl_event.view_id = flutter::kFlutterImplicitViewId;
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
}
@@ -832,6 +837,10 @@ void fl_engine_send_pointer_pan_zoom_event(FlEngine* self,
fl_event.rotation = rotation;
fl_event.device = kPointerPanZoomDeviceId;
fl_event.device_kind = kFlutterPointerDeviceKindTrackpad;
// TODO(dkwingsmt): Assign the correct view ID once the Linux embedder
// supports multiple views.
// https://github.com/flutter/flutter/issues/138178
fl_event.view_id = flutter::kFlutterImplicitViewId;
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
}

View File

@@ -6,6 +6,7 @@
#include <chrono>
#include "flutter/common/constants.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "flutter/shell/platform/common/accessibility_bridge.h"
#include "flutter/shell/platform/windows/keyboard_key_channel_handler.h"
@@ -547,6 +548,10 @@ void FlutterWindowsView::SendPointerEventWithData(
event.device_kind = state->device_kind;
event.device = state->pointer_id;
event.buttons = state->buttons;
// TODO(dkwingsmt): Use the correct view ID for pointer events once the
// Windows embedder supports multiple views.
// https://github.com/flutter/flutter/issues/138179
event.view_id = flutter::kFlutterImplicitViewId;
// Set metadata that's always the same regardless of the event.
event.struct_size = sizeof(event);