Refactor FlKeyboardHandler (#55601)

Refactor FlKeyboardHandler to be more GObject and less C++.

Done as we will need to move the handler from FlView to FlEngine to
support multi-view.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index 9be3b1f..b7119de 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -44809,6 +44809,8 @@
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/linux/fl_message_codec.cc + ../../../flutter/LICENSE
@@ -47715,6 +47717,8 @@
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler.h
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc
+FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc
 FILE: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.h
 FILE: ../../../flutter/shell/platform/linux/fl_message_codec.cc
diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn
index 13511e9..36045a9 100644
--- a/shell/platform/linux/BUILD.gn
+++ b/shell/platform/linux/BUILD.gn
@@ -81,6 +81,7 @@
              "fl_dart_project_private.h",
              "fl_engine_private.h",
              "fl_keyboard_handler.h",
+             "fl_keyboard_pending_event.h",
              "fl_keyboard_view_delegate.h",
              "fl_key_event.h",
              "fl_key_responder.h",
@@ -116,6 +117,7 @@
     "fl_key_event.cc",
     "fl_key_responder.cc",
     "fl_keyboard_handler.cc",
+    "fl_keyboard_pending_event.cc",
     "fl_keyboard_view_delegate.cc",
     "fl_message_codec.cc",
     "fl_method_call.cc",
diff --git a/shell/platform/linux/fl_key_channel_responder.cc b/shell/platform/linux/fl_key_channel_responder.cc
index c79ac94..8c6e28b 100644
--- a/shell/platform/linux/fl_key_channel_responder.cc
+++ b/shell/platform/linux/fl_key_channel_responder.cc
@@ -212,9 +212,11 @@
   g_return_if_fail(event != nullptr);
   g_return_if_fail(callback != nullptr);
 
-  const gchar* type = event->is_press ? kTypeValueDown : kTypeValueUp;
-  int64_t scan_code = event->keycode;
-  int64_t unicode_scarlar_values = gdk_keyval_to_unicode(event->keyval);
+  const gchar* type =
+      fl_key_event_get_is_press(event) ? kTypeValueDown : kTypeValueUp;
+  int64_t scan_code = fl_key_event_get_keycode(event);
+  int64_t unicode_scarlar_values =
+      gdk_keyval_to_unicode(fl_key_event_get_keyval(event));
 
   // For most modifier keys, GTK keeps track of the "pressed" state of the
   // modifier keys. Flutter uses this information to keep modifier keys from
@@ -239,20 +241,21 @@
   // interactions (for example, if shift-lock is on, tab traversal is broken).
 
   // Remove lock states from state mask.
-  guint state = event->state & ~(GDK_LOCK_MASK | GDK_MOD2_MASK);
+  guint state =
+      fl_key_event_get_state(event) & ~(GDK_LOCK_MASK | GDK_MOD2_MASK);
 
   static bool shift_lock_pressed = FALSE;
   static bool caps_lock_pressed = FALSE;
   static bool num_lock_pressed = FALSE;
-  switch (event->keyval) {
+  switch (fl_key_event_get_keyval(event)) {
     case GDK_KEY_Num_Lock:
-      num_lock_pressed = event->is_press;
+      num_lock_pressed = fl_key_event_get_is_press(event);
       break;
     case GDK_KEY_Caps_Lock:
-      caps_lock_pressed = event->is_press;
+      caps_lock_pressed = fl_key_event_get_is_press(event);
       break;
     case GDK_KEY_Shift_Lock:
-      shift_lock_pressed = event->is_press;
+      shift_lock_pressed = fl_key_event_get_is_press(event);
       break;
   }
 
@@ -269,7 +272,7 @@
   fl_value_set_string_take(message, kToolkitKey,
                            fl_value_new_string(kGtkToolkit));
   fl_value_set_string_take(message, kKeyCodeKey,
-                           fl_value_new_int(event->keyval));
+                           fl_value_new_int(fl_key_event_get_keyval(event)));
   fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(state));
   if (unicode_scarlar_values != 0) {
     fl_value_set_string_take(message, kUnicodeScalarValuesKey,
diff --git a/shell/platform/linux/fl_key_channel_responder_test.cc b/shell/platform/linux/fl_key_channel_responder_test.cc
index 4972f59..2d38456 100644
--- a/shell/platform/linux/fl_key_channel_responder_test.cc
+++ b/shell/platform/linux/fl_key_channel_responder_test.cc
@@ -29,33 +29,6 @@
   g_main_loop_quit(static_cast<GMainLoop*>(user_data));
 }
 
-namespace {
-// A global variable to store new event. It is a global variable so that it can
-// be returned by #fl_key_event_new_by_mock for easy use.
-FlKeyEvent _g_key_event;
-}  // namespace
-
-// Create a new #FlKeyEvent with the given information.
-//
-// This event is passed to #fl_key_responder_handle_event,
-// which assumes that the event is managed by callee.
-// Therefore #fl_key_event_new_by_mock doesn't need to
-// dynamically allocate, but reuses the same global object.
-static FlKeyEvent* fl_key_event_new_by_mock(guint32 time_in_milliseconds,
-                                            bool is_press,
-                                            guint keyval,
-                                            guint16 keycode,
-                                            GdkModifierType state,
-                                            gboolean is_modifier) {
-  _g_key_event.is_press = is_press;
-  _g_key_event.time = time_in_milliseconds;
-  _g_key_event.state = state;
-  _g_key_event.keyval = keyval;
-  _g_key_event.keycode = keycode;
-  _g_key_event.origin = nullptr;
-  return &_g_key_event;
-}
-
 // Test sending a letter "A";
 TEST(FlKeyChannelResponderTest, SendKeyEvent) {
   g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
@@ -69,11 +42,9 @@
   g_autoptr(FlKeyResponder) responder =
       FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04,
-                               static_cast<GdkModifierType>(0), false),
-      responder_callback, loop);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      12345, TRUE, 0x04, GDK_KEY_A, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, responder_callback, loop);
   expected_value =
       "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, "
       "modifiers: 0, unicodeScalarValues: 65}";
@@ -82,11 +53,9 @@
   // Blocks here until echo_response_cb is called.
   g_main_loop_run(loop);
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(23456, false, GDK_KEY_A, 0x04,
-                               static_cast<GdkModifierType>(0), false),
-      responder_callback, loop);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      23456, FALSE, 0x04, GDK_KEY_A, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event2, responder_callback, loop);
   expected_value =
       "{type: keyup, keymap: linux, scanCode: 4, toolkit: gtk, keyCode: 65, "
       "modifiers: 0, unicodeScalarValues: 65}";
@@ -110,11 +79,9 @@
   g_autoptr(FlKeyResponder) responder =
       FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12345, true, key_code, 0x04,
-                               static_cast<GdkModifierType>(0), false),
-      responder_callback, loop);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      12345, TRUE, 0x04, key_code, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, responder_callback, loop);
   expected_value = down_expected;
   expected_handled = FALSE;
 
@@ -123,11 +90,9 @@
 
   expected_value = up_expected;
   expected_handled = FALSE;
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12346, false, key_code, 0x04,
-                               static_cast<GdkModifierType>(0), false),
-      responder_callback, loop);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      12346, FALSE, 0x04, key_code, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event2, responder_callback, loop);
 
   // Blocks here until echo_response_cb is called.
   g_main_loop_run(loop);
@@ -172,11 +137,9 @@
   g_autoptr(FlKeyResponder) responder =
       FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04,
-                               static_cast<GdkModifierType>(0), false),
-      responder_callback, loop);
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      12345, TRUE, 0x04, GDK_KEY_A, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event, responder_callback, loop);
   expected_handled = TRUE;
   expected_value =
       "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
@@ -198,11 +161,10 @@
   g_autoptr(FlKeyResponder) responder =
       FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04,
-                               static_cast<GdkModifierType>(0), false),
-      responder_callback, loop, 888);
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      12345, TRUE, 0x04, GDK_KEY_A, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event, responder_callback, loop,
+                                888);
   expected_handled = TRUE;
   expected_value =
       "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc
index 100d618..922922a 100644
--- a/shell/platform/linux/fl_key_embedder_responder.cc
+++ b/shell/platform/linux/fl_key_embedder_responder.cc
@@ -297,16 +297,16 @@
   return (logical_id & kValueMask) | plane;
 }
 
-static uint64_t event_to_physical_key(const FlKeyEvent* event) {
-  auto found = xkb_to_physical_key_map.find(event->keycode);
+static uint64_t event_to_physical_key(FlKeyEvent* event) {
+  auto found = xkb_to_physical_key_map.find(fl_key_event_get_keycode(event));
   if (found != xkb_to_physical_key_map.end()) {
     return found->second;
   }
-  return apply_id_plane(event->keycode, kGtkPlane);
+  return apply_id_plane(fl_key_event_get_keycode(event), kGtkPlane);
 }
 
-static uint64_t event_to_logical_key(const FlKeyEvent* event) {
-  guint keyval = event->keyval;
+static uint64_t event_to_logical_key(FlKeyEvent* event) {
+  guint keyval = fl_key_event_get_keyval(event);
   auto found = gtk_keyval_to_logical_key_map.find(keyval);
   if (found != gtk_keyval_to_logical_key_map.end()) {
     return found->second;
@@ -319,14 +319,15 @@
   return apply_id_plane(keyval, kGtkPlane);
 }
 
-static uint64_t event_to_timestamp(const FlKeyEvent* event) {
-  return kMicrosecondsPerMillisecond * static_cast<double>(event->time);
+static uint64_t event_to_timestamp(FlKeyEvent* event) {
+  return kMicrosecondsPerMillisecond *
+         static_cast<double>(fl_key_event_get_time(event));
 }
 
-// Returns a newly accocated UTF-8 string from event->keyval that must be
-// freed later with g_free().
-static char* event_to_character(const FlKeyEvent* event) {
-  gunichar unicodeChar = gdk_keyval_to_unicode(event->keyval);
+// Returns a newly accocated UTF-8 string from fl_key_event_get_keyval(event)
+// that must be freed later with g_free().
+static char* event_to_character(FlKeyEvent* event) {
+  gunichar unicodeChar = gdk_keyval_to_unicode(fl_key_event_get_keyval(event));
   glong items_written;
   gchar* result = g_ucs4_to_utf8(&unicodeChar, 1, NULL, &items_written, NULL);
   if (items_written == 0) {
@@ -790,11 +791,11 @@
   const uint64_t physical_key = corrected_modifier_physical_key(
       self->modifier_bit_to_checked_keys, physical_key_from_event, logical_key);
   const double timestamp = event_to_timestamp(event);
-  const bool is_down_event = event->is_press;
+  const bool is_down_event = fl_key_event_get_is_press(event);
 
   SyncStateLoopContext sync_state_context;
   sync_state_context.self = self;
-  sync_state_context.state = event->state;
+  sync_state_context.state = fl_key_event_get_state(event);
   sync_state_context.timestamp = timestamp;
   sync_state_context.is_down = is_down_event;
   sync_state_context.event_logical_key = logical_key;
diff --git a/shell/platform/linux/fl_key_embedder_responder_test.cc b/shell/platform/linux/fl_key_embedder_responder_test.cc
index aac675d..3a1ab31 100644
--- a/shell/platform/linux/fl_key_embedder_responder_test.cc
+++ b/shell/platform/linux/fl_key_embedder_responder_test.cc
@@ -16,9 +16,6 @@
 constexpr gboolean kRelease = FALSE;
 constexpr gboolean kPress = TRUE;
 
-constexpr gboolean kIsModifier = TRUE;
-constexpr gboolean kIsNotModifier = FALSE;
-
 constexpr guint16 kKeyCodeDigit1 = 0x0au;
 constexpr guint16 kKeyCodeKeyA = 0x26u;
 constexpr guint16 kKeyCodeShiftLeft = 0x32u;
@@ -100,33 +97,6 @@
   return self;
 }
 
-namespace {
-// A global variable to store new event. It is a global variable so that it can
-// be returned by #fl_key_event_new_by_mock for easy use.
-FlKeyEvent _g_key_event;
-}  // namespace
-
-// Create a new #FlKeyEvent with the given information.
-//
-// This event is passed to #fl_key_responder_handle_event,
-// which assumes that the event is managed by callee.
-// Therefore #fl_key_event_new_by_mock doesn't need to
-// dynamically allocate, but reuses the same global object.
-static FlKeyEvent* fl_key_event_new_by_mock(guint32 time_in_milliseconds,
-                                            bool is_press,
-                                            guint keyval,
-                                            guint16 keycode,
-                                            GdkModifierType state,
-                                            gboolean is_modifier) {
-  _g_key_event.is_press = is_press;
-  _g_key_event.time = time_in_milliseconds;
-  _g_key_event.state = state;
-  _g_key_event.keyval = keyval;
-  _g_key_event.keycode = keycode;
-  _g_key_event.origin = nullptr;
-  return &_g_key_event;
-}
-
 static gboolean g_expected_handled;
 static gpointer g_expected_user_data;
 
@@ -176,11 +146,11 @@
 
   // On a QWERTY keyboard, press key Q (physically key A), and release.
   // Key down
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12345, kPress, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 =
+      fl_key_event_new(12345, kPress, kKeyCodeKeyA, GDK_KEY_a,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -196,11 +166,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Key up
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12346, kRelease, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(12346, kRelease, kKeyCodeKeyA, GDK_KEY_a,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -217,11 +187,11 @@
 
   // On an AZERTY keyboard, press key Q (physically key A), and release.
   // Key down
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12347, kPress, GDK_KEY_q, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 =
+      fl_key_event_new(12347, kPress, kKeyCodeKeyA, GDK_KEY_q,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -237,11 +207,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Key up
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12348, kRelease, GDK_KEY_q, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 =
+      fl_key_event_new(12348, kRelease, kKeyCodeKeyA, GDK_KEY_q,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -272,11 +242,11 @@
 
   // On an AZERTY keyboard, press physical key 1, and release.
   // Key down
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(12345, kPress, GDK_KEY_ampersand, kKeyCodeDigit1,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data, kLogicalDigit1);
+  g_autoptr(FlKeyEvent) event =
+      fl_key_event_new(12345, kPress, kKeyCodeDigit1, GDK_KEY_ampersand,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event, verify_response_handled,
+                                &user_data, kLogicalDigit1);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -306,11 +276,11 @@
   FlKeyEmbedderCallRecord* record;
 
   // Press shift right
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_Shift_R, kKeyCodeShiftRight,
-                               static_cast<GdkModifierType>(0), kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 =
+      fl_key_event_new(101, kPress, kKeyCodeShiftRight, GDK_KEY_Shift_R,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -324,11 +294,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press key A
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA,
-                               GDK_SHIFT_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_SHIFT_MASK, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -342,11 +311,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release shift right
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Shift_R,
-                               kKeyCodeShiftRight, GDK_SHIFT_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      103, kRelease, kKeyCodeShiftRight, GDK_KEY_Shift_R, GDK_SHIFT_MASK, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -360,11 +328,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Release key A
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_A, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 =
+      fl_key_event_new(104, kRelease, kKeyCodeKeyA, GDK_KEY_A,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -399,11 +367,11 @@
   FlKeyEmbedderCallRecord* record;
 
   // Press Numpad 1 (stage 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_KP_End, kKeyCodeNumpad1,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 =
+      fl_key_event_new(101, kPress, kKeyCodeNumpad1, GDK_KEY_KP_End,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -417,11 +385,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Press NumLock (stage 0 -> 1)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kPress, kKeyCodeNumLock, GDK_KEY_Num_Lock,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -435,11 +403,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release numpad 1 (stage 1)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_KP_1, kKeyCodeNumpad1,
-                               GDK_MOD2_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      104, kRelease, kKeyCodeNumpad1, GDK_KEY_KP_1, GDK_MOD2_MASK, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -453,11 +420,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release NumLock (stage 1 -> 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               GDK_MOD2_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
+      103, kRelease, kKeyCodeNumLock, GDK_KEY_Num_Lock, GDK_MOD2_MASK, 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -471,11 +437,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press Numpad 1 (stage 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_KP_End, kKeyCodeNumpad1,
-                               GDK_MOD2_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event5 = fl_key_event_new(
+      101, kPress, kKeyCodeNumpad1, GDK_KEY_KP_End, GDK_MOD2_MASK, 0);
+  fl_key_responder_handle_event(responder, event5, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -489,11 +454,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press NumLock (stage 2 -> 3)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               GDK_MOD2_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event6 = fl_key_event_new(
+      102, kPress, kKeyCodeNumLock, GDK_KEY_Num_Lock, GDK_MOD2_MASK, 0);
+  fl_key_responder_handle_event(responder, event6, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -507,11 +471,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release numpad 1 (stage 3)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_KP_1, kKeyCodeNumpad1,
-                               GDK_MOD2_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event7 = fl_key_event_new(
+      104, kRelease, kKeyCodeNumpad1, GDK_KEY_KP_1, GDK_MOD2_MASK, 0);
+  fl_key_responder_handle_event(responder, event7, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -525,11 +488,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release NumLock (stage 3 -> 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               GDK_MOD2_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event8 = fl_key_event_new(
+      103, kRelease, kKeyCodeNumLock, GDK_KEY_Num_Lock, GDK_MOD2_MASK, 0);
+  fl_key_responder_handle_event(responder, event8, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -562,11 +524,10 @@
   GdkModifierType state = static_cast<GdkModifierType>(0);
 
   // Press shift left
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_Shift_L, kKeyCodeShiftLeft,
-                               state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      101, kPress, kKeyCodeShiftLeft, GDK_KEY_Shift_L, state, 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -582,11 +543,10 @@
   state = GDK_SHIFT_MASK;
 
   // Press digit 1, which is '!' on a US keyboard
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_exclam, kKeyCodeDigit1,
-                               state, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kPress, kKeyCodeDigit1, GDK_KEY_exclam, state, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -600,11 +560,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release shift
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Shift_L,
-                               kKeyCodeShiftLeft, state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      103, kRelease, kKeyCodeShiftLeft, GDK_KEY_Shift_L, state, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -620,11 +579,10 @@
   state = static_cast<GdkModifierType>(0);
 
   // Release digit 1, which is "1" because shift has been released.
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_1, kKeyCodeDigit1, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 =
+      fl_key_event_new(104, kRelease, kKeyCodeDigit1, GDK_KEY_1, state, 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -655,11 +613,11 @@
   FlKeyEmbedderCallRecord* record;
 
   // Press CapsLock (stage 0 -> 1)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock,
-                               static_cast<GdkModifierType>(0), kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 =
+      fl_key_event_new(101, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -673,11 +631,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press key A (stage 1)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA,
-                               GDK_LOCK_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -691,11 +648,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release CapsLock (stage 1 -> 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Caps_Lock,
-                               kKeyCodeCapsLock, GDK_LOCK_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      103, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -709,11 +665,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release key A (stage 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_A, kKeyCodeKeyA,
-                               GDK_LOCK_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(104, kRelease, kKeyCodeKeyA,
+                                                  GDK_KEY_A, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -727,11 +682,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press CapsLock (stage 2 -> 3)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(105, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock,
-                               GDK_LOCK_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event5 = fl_key_event_new(
+      105, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event5, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -745,11 +699,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press key A (stage 3)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(106, kPress, GDK_KEY_A, kKeyCodeKeyA,
-                               GDK_LOCK_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event6 =
+      fl_key_event_new(106, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event6, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -763,11 +716,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release CapsLock (stage 3 -> 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(107, kRelease, GDK_KEY_Caps_Lock,
-                               kKeyCodeCapsLock, GDK_LOCK_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event7 = fl_key_event_new(
+      107, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event7, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -781,11 +733,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Release key A (stage 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(108, kRelease, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event8 =
+      fl_key_event_new(108, kRelease, kKeyCodeKeyA, GDK_KEY_a,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event8, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -816,11 +768,10 @@
   FlKeyEmbedderCallRecord* record;
 
   // Press key A (stage 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      101, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -834,11 +785,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press CapsLock (stage 0 -> 1)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock,
-                               GDK_LOCK_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      102, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -852,11 +802,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release CapsLock (stage 1 -> 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Caps_Lock,
-                               kKeyCodeCapsLock, GDK_LOCK_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      103, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -870,11 +819,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release key A (stage 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_A, kKeyCodeKeyA,
-                               GDK_LOCK_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(104, kRelease, kKeyCodeKeyA,
+                                                  GDK_KEY_A, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -888,11 +836,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Press key A (stage 2)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(105, kPress, GDK_KEY_A, kKeyCodeKeyA,
-                               GDK_LOCK_MASK, kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event5 =
+      fl_key_event_new(105, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event5, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -906,11 +853,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Press CapsLock (stage 2 -> 3)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(106, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock,
-                               static_cast<GdkModifierType>(0), kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event6 =
+      fl_key_event_new(106, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event6, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -924,11 +871,10 @@
   g_ptr_array_clear(g_call_records);
 
   // Release CapsLock (stage 3 -> 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(107, kRelease, GDK_KEY_Caps_Lock,
-                               kKeyCodeCapsLock, GDK_LOCK_MASK, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event7 = fl_key_event_new(
+      107, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0);
+  fl_key_responder_handle_event(responder, event7, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -942,11 +888,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Release key A (stage 0)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(108, kRelease, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event8 =
+      fl_key_event_new(108, kRelease, kKeyCodeKeyA, GDK_KEY_a,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event8, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -973,11 +919,10 @@
   FlKeyEmbedderCallRecord* record;
 
   // Press KeyA
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      101, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
 
@@ -987,11 +932,10 @@
 
   // Another KeyA down events, which usually means a repeated event.
   g_expected_handled = false;
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      102, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
 
@@ -1007,11 +951,11 @@
   g_ptr_array_clear(g_call_records);
 
   // Release KeyA
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_q, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 =
+      fl_key_event_new(103, kRelease, kKeyCodeKeyA, GDK_KEY_q,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1033,11 +977,11 @@
 
   // Release KeyA before it was even pressed.
   g_expected_handled = true;  // The empty event is always handled.
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_q, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event =
+      fl_key_event_new(103, kRelease, kKeyCodeKeyA, GDK_KEY_q,
+                       static_cast<GdkModifierType>(0), 0);
+  fl_key_responder_handle_event(responder, event, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
 
@@ -1069,11 +1013,10 @@
   GdkModifierType state = GDK_CONTROL_MASK;
 
   // Send a ControlLeft up
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kRelease, GDK_KEY_Control_L,
-                               kKeyCodeControlLeft, state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      101, kRelease, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 2u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1099,11 +1042,10 @@
 
   // Send a ControlLeft down.
   state = static_cast<GdkModifierType>(0);
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_Control_L,
-                               kKeyCodeControlLeft, state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      102, kPress, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
   invoke_record_callback_and_verify(record, TRUE, &user_data);
@@ -1113,11 +1055,10 @@
   state = static_cast<GdkModifierType>(0);
 
   // Send another ControlLeft down
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kPress, GDK_KEY_Control_L,
-                               kKeyCodeControlLeft, state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      103, kPress, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 2u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1141,11 +1082,10 @@
 
   // Send a ControlLeft up to clear up state.
   state = GDK_CONTROL_MASK;
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(104, kRelease, GDK_KEY_Control_L,
-                               kKeyCodeControlLeft, state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
+      104, kRelease, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
   invoke_record_callback_and_verify(record, TRUE, &user_data);
@@ -1157,11 +1097,10 @@
   state = GDK_CONTROL_MASK;
 
   // Send a ControlRight up.
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(105, kRelease, GDK_KEY_Control_R,
-                               kKeyCodeControlRight, state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event5 = fl_key_event_new(
+      105, kRelease, kKeyCodeControlRight, GDK_KEY_Control_R, state, 0);
+  fl_key_responder_handle_event(responder, event5, verify_response_handled,
+                                &user_data);
 
   // A ControlLeft down is synthesized, with an empty event.
   // Reason: The ControlLeft down is synthesized to synchronize the state
@@ -1198,11 +1137,10 @@
   GdkModifierType state = GDK_CONTROL_MASK;
 
   // Send a normal event (KeyA down)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 =
+      fl_key_event_new(101, kPress, kKeyCodeKeyA, GDK_KEY_a, state, 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 2u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1228,11 +1166,10 @@
   state = static_cast<GdkModifierType>(0);
 
   // Send a normal event (KeyA up)
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kRelease, GDK_KEY_A, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kRelease, kKeyCodeKeyA, GDK_KEY_A, state, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 2u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1259,11 +1196,10 @@
   // Press a key with physical CapsLock and logical ControlLeft.
   state = static_cast<GdkModifierType>(0);
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_Control_L, kKeyCodeCapsLock,
-                               state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(101, kPress, kKeyCodeCapsLock,
+                                                  GDK_KEY_Control_L, state, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1281,11 +1217,10 @@
   state = static_cast<GdkModifierType>(0);
 
   // Send a normal event (KeyA down).
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event4 =
+      fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, state, 0);
+  fl_key_responder_handle_event(responder, event4, verify_response_handled,
+                                &user_data);
 
   // The synthesized event should have physical CapsLock and logical
   // ControlLeft.
@@ -1328,11 +1263,10 @@
   // Press a key with physical CapsLock and logical ControlLeft.
   GdkModifierType state = static_cast<GdkModifierType>(0);
 
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_Control_L, kKeyCodeCapsLock,
-                               state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeCapsLock,
+                                                  GDK_KEY_Control_L, state, 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1350,11 +1284,10 @@
   state = static_cast<GdkModifierType>(0);
 
   // Send a normal event (KeyA down).
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, state, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   // The synthesized event should have physical CapsLock and logical
   // ControlLeft.
@@ -1397,11 +1330,10 @@
   GdkModifierType state = GDK_MOD2_MASK;
 
   // Send a normal event
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 =
+      fl_key_event_new(101, kPress, kKeyCodeKeyA, GDK_KEY_a, state, 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 2u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1427,11 +1359,10 @@
   state = static_cast<GdkModifierType>(0);
 
   // Release key A
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kRelease, GDK_KEY_A, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 =
+      fl_key_event_new(102, kRelease, kKeyCodeKeyA, GDK_KEY_A, state, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 4u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1472,11 +1403,10 @@
   // Release NumLock. Since the previous event should have synthesized NumLock
   // to be released, this should result in only an empty event.
   g_expected_handled = true;
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      103, kRelease, kKeyCodeNumLock, GDK_KEY_Num_Lock, state, 0);
+  fl_key_responder_handle_event(responder, event3, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 1u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1505,11 +1435,10 @@
   GdkModifierType state = GDK_MOD2_MASK;
 
   // NumLock down
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeNumLock,
+                                                  GDK_KEY_Num_Lock, state, 0);
+  fl_key_responder_handle_event(responder, event1, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 3u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1543,11 +1472,10 @@
   state = GDK_MOD2_MASK;
 
   // NumLock up
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(102, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock,
-                               state, kIsModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeNumLock,
+                                                  GDK_KEY_Num_Lock, state, 0);
+  fl_key_responder_handle_event(responder, event2, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 4u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1606,11 +1534,10 @@
 
   // Send a KeyA up event, which will be ignored.
   g_expected_handled = true;  // The ignored event is always handled.
-  fl_key_responder_handle_event(
-      responder,
-      fl_key_event_new_by_mock(101, kRelease, GDK_KEY_a, kKeyCodeKeyA, state,
-                               kIsNotModifier),
-      verify_response_handled, &user_data);
+  g_autoptr(FlKeyEvent) event =
+      fl_key_event_new(101, kRelease, kKeyCodeKeyA, GDK_KEY_a, state, 0);
+  fl_key_responder_handle_event(responder, event, verify_response_handled,
+                                &user_data);
 
   EXPECT_EQ(g_call_records->len, 2u);
   record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
@@ -1659,11 +1586,10 @@
                                                GdkModifierType state) {
     now_time += 1;
     int user_data = 123;  // Arbitrary user data
-    fl_key_responder_handle_event(
-        responder,
-        fl_key_event_new_by_mock(now_time, is_press, keyval, keycode, state,
-                                 kIsModifier),
-        verify_response_handled, &user_data);
+    g_autoptr(FlKeyEvent) event =
+        fl_key_event_new(now_time, is_press, keycode, keyval, state, 0);
+    fl_key_responder_handle_event(responder, event, verify_response_handled,
+                                  &user_data);
   };
 
   FlKeyEmbedderCallRecord* record;
@@ -1768,11 +1694,10 @@
                                                GdkModifierType state) {
     now_time += 1;
     int user_data = 123;  // Arbitrary user data
-    fl_key_responder_handle_event(
-        responder,
-        fl_key_event_new_by_mock(now_time, is_press, keyval, keycode, state,
-                                 kIsModifier),
-        verify_response_handled, &user_data);
+    g_autoptr(FlKeyEvent) event =
+        fl_key_event_new(now_time, is_press, keycode, keyval, state, 0);
+    fl_key_responder_handle_event(responder, event, verify_response_handled,
+                                  &user_data);
   };
 
   FlKeyEmbedderCallRecord* record;
diff --git a/shell/platform/linux/fl_key_event.cc b/shell/platform/linux/fl_key_event.cc
index 230bd9c..b79e1b9 100644
--- a/shell/platform/linux/fl_key_event.cc
+++ b/shell/platform/linux/fl_key_event.cc
@@ -4,12 +4,59 @@
 
 #include "flutter/shell/platform/linux/fl_key_event.h"
 
+struct _FlKeyEvent {
+  GObject parent_instance;
+
+  // Time in milliseconds.
+  guint32 time;
+
+  // True if is a press event, otherwise a release event.
+  gboolean is_press;
+
+  // Hardware keycode.
+  guint16 keycode;
+
+  // Keyval.
+  guint keyval;
+
+  // Modifier state.
+  GdkModifierType state;
+
+  // Keyboard group.
+  guint8 group;
+
+  // The original event.
+  GdkEvent* origin;
+};
+
+G_DEFINE_TYPE(FlKeyEvent, fl_key_event, G_TYPE_OBJECT)
+
+FlKeyEvent* fl_key_event_new(guint32 time,
+                             gboolean is_press,
+                             guint16 keycode,
+                             guint keyval,
+                             GdkModifierType state,
+                             guint8 group) {
+  FlKeyEvent* self =
+      FL_KEY_EVENT(g_object_new(fl_key_event_get_type(), nullptr));
+
+  self->time = time;
+  self->is_press = is_press;
+  self->keycode = keycode;
+  self->keyval = keyval;
+  self->state = state;
+  self->group = group;
+
+  return self;
+}
+
 FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* event) {
-  g_return_val_if_fail(event != nullptr, nullptr);
+  FlKeyEvent* self =
+      FL_KEY_EVENT(g_object_new(fl_key_event_get_type(), nullptr));
+
   GdkEventType type = gdk_event_get_event_type(event);
   g_return_val_if_fail(type == GDK_KEY_PRESS || type == GDK_KEY_RELEASE,
                        nullptr);
-  FlKeyEvent* result = g_new(FlKeyEvent, 1);
 
   guint16 keycode = 0;
   gdk_event_get_keycode(event, &keycode);
@@ -18,26 +65,76 @@
   GdkModifierType state = static_cast<GdkModifierType>(0);
   gdk_event_get_state(event, &state);
 
-  result->time = gdk_event_get_time(event);
-  result->is_press = type == GDK_KEY_PRESS;
-  result->keycode = keycode;
-  result->keyval = keyval;
-  result->state = state;
-  result->group = event->key.group;
-  result->origin = event;
+  self->time = gdk_event_get_time(event);
+  self->is_press = type == GDK_KEY_PRESS;
+  self->keycode = keycode;
+  self->keyval = keyval;
+  self->state = state;
+  self->group = event->key.group;
+  self->origin = event;
 
-  return result;
+  return self;
 }
 
-void fl_key_event_dispose(FlKeyEvent* event) {
-  if (event->origin != nullptr) {
-    gdk_event_free(event->origin);
-  }
-  g_free(event);
+guint32 fl_key_event_get_time(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), 0);
+  return self->time;
 }
 
-FlKeyEvent* fl_key_event_clone(const FlKeyEvent* event) {
-  FlKeyEvent* new_event = g_new(FlKeyEvent, 1);
-  *new_event = *event;
-  return new_event;
+gboolean fl_key_event_get_is_press(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), FALSE);
+  return self->is_press;
 }
+
+guint16 fl_key_event_get_keycode(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), 0);
+  return self->keycode;
+}
+
+guint fl_key_event_get_keyval(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), 0);
+  return self->keyval;
+}
+
+GdkModifierType fl_key_event_get_state(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), static_cast<GdkModifierType>(0));
+  return self->state;
+}
+
+guint8 fl_key_event_get_group(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), 0);
+  return self->group;
+}
+
+GdkEvent* fl_key_event_get_origin(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), nullptr);
+  return self->origin;
+}
+
+uint64_t fl_key_event_hash(FlKeyEvent* self) {
+  g_return_val_if_fail(FL_IS_KEY_EVENT(self), 0);
+
+  // Combine the event timestamp, the type of event, and the hardware keycode
+  // (scan code) of the event to come up with a unique id for this event that
+  // can be derived solely from the event data itself, so that we can identify
+  // whether or not we have seen this event already.
+  guint64 type =
+      static_cast<uint64_t>(self->is_press ? GDK_KEY_PRESS : GDK_KEY_RELEASE);
+  guint64 keycode = static_cast<uint64_t>(self->keycode);
+  return (self->time & 0xffffffff) | ((type & 0xffff) << 32) |
+         ((keycode & 0xffff) << 48);
+}
+
+static void fl_key_event_dispose(GObject* object) {
+  FlKeyEvent* self = FL_KEY_EVENT(object);
+
+  g_clear_pointer(&self->origin, gdk_event_free);
+
+  G_OBJECT_CLASS(fl_key_event_parent_class)->dispose(object);
+}
+
+static void fl_key_event_class_init(FlKeyEventClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_key_event_dispose;
+}
+
+static void fl_key_event_init(FlKeyEvent* self) {}
diff --git a/shell/platform/linux/fl_key_event.h b/shell/platform/linux/fl_key_event.h
index 2a3a433..80b5675 100644
--- a/shell/platform/linux/fl_key_event.h
+++ b/shell/platform/linux/fl_key_event.h
@@ -5,8 +5,12 @@
 #ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_H_
 #define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_H_
 
+#include <stdint.h>
+
 #include <gdk/gdk.h>
 
+G_DECLARE_FINAL_TYPE(FlKeyEvent, fl_key_event, FL, KEY_EVENT, GObject);
+
 /**
  * FlKeyEvent:
  * A struct that stores information from GdkEvent.
@@ -19,42 +23,47 @@
  * object, so that Flutter can create an event object in unit tests even after
  * migrating to GDK 4.0 which stops supporting creating GdkEvent.
  */
-typedef struct _FlKeyEvent {
-  // Time in milliseconds.
-  guint32 time;
-  // True if is a press event, otherwise a release event.
-  bool is_press;
-  // Hardware keycode.
-  guint16 keycode;
-  // Keyval.
-  guint keyval;
-  // Modifier state.
-  GdkModifierType state;
-  // Keyboard group.
-  guint8 group;
-  // The original event.
-  GdkEvent* origin;
-} FlKeyEvent;
+
+FlKeyEvent* fl_key_event_new(guint32 time,
+                             gboolean is_press,
+                             guint16 keycode,
+                             guint keyval,
+                             GdkModifierType state,
+                             guint8 group);
 
 /**
  * fl_key_event_new_from_gdk_event:
- * @event: the #GdkEvent this #FlKeyEvent is based on. The #event must be a
- * #GdkEventKey, and will be destroyed by #fl_key_event_dispose.
+ * @event: the #GdkEvent this #FlKeyEvent is based on.
  *
  * Create a new #FlKeyEvent based on a #GdkEvent.
  *
- * Returns: a new #FlKeyEvent. Must be freed with #fl_key_event_dispose.
+ * Returns: a new #FlKeyEvent.
  */
 FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* event);
 
-/**
- * fl_key_event_dispose:
- * @event: the event to dispose.
- *
- * Properly disposes the content of #event and then the pointer.
- */
-void fl_key_event_dispose(FlKeyEvent* event);
+guint32 fl_key_event_get_time(FlKeyEvent* event);
 
-FlKeyEvent* fl_key_event_clone(const FlKeyEvent* source);
+gboolean fl_key_event_get_is_press(FlKeyEvent* event);
+
+guint16 fl_key_event_get_keycode(FlKeyEvent* event);
+
+guint fl_key_event_get_keyval(FlKeyEvent* event);
+
+GdkModifierType fl_key_event_get_state(FlKeyEvent* event);
+
+guint8 fl_key_event_get_group(FlKeyEvent* event);
+
+GdkEvent* fl_key_event_get_origin(FlKeyEvent* event);
+
+/**
+ * fl_key_event_hash:
+ * @event: an #FlKeyEvent.
+ *
+ * Calculates a unique ID for a given FlKeyEvent object to use for
+ * identification of responses from the framework.
+ *
+ * Returns: a hash code.
+ */
+uint64_t fl_key_event_hash(FlKeyEvent* event);
 
 #endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EVENT_H_
diff --git a/shell/platform/linux/fl_keyboard_handler.cc b/shell/platform/linux/fl_keyboard_handler.cc
index 7597213..46d4547 100644
--- a/shell/platform/linux/fl_keyboard_handler.cc
+++ b/shell/platform/linux/fl_keyboard_handler.cc
@@ -11,6 +11,7 @@
 
 #include "flutter/shell/platform/linux/fl_key_channel_responder.h"
 #include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
+#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h"
 #include "flutter/shell/platform/linux/key_mapping.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
 #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
@@ -24,12 +25,6 @@
 
 /* Declarations of private classes */
 
-G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
-                     fl_keyboard_pending_event,
-                     FL,
-                     KEYBOARD_PENDING_EVENT,
-                     GObject);
-
 #define FL_TYPE_KEYBOARD_HANDLER_USER_DATA \
   fl_keyboard_handler_user_data_get_type()
 G_DECLARE_FINAL_TYPE(FlKeyboardHandlerUserData,
@@ -94,10 +89,10 @@
 
 }  // namespace
 
-static uint64_t get_logical_key_from_layout(const FlKeyEvent* event,
+static uint64_t get_logical_key_from_layout(FlKeyEvent* event,
                                             const DerivedLayout& layout) {
-  guint8 group = event->group;
-  guint16 keycode = event->keycode;
+  guint8 group = fl_key_event_get_group(event);
+  guint16 keycode = fl_key_event_get_keycode(event);
   if (keycode >= kLayoutSize) {
     return 0;
   }
@@ -109,93 +104,6 @@
   return 0;
 }
 
-/* Define FlKeyboardPendingEvent */
-
-/**
- * FlKeyboardPendingEvent:
- * A record for events that have been received by the handler, but
- * dispatched to other objects, whose results have yet to return.
- *
- * This object is used by both the "pending_responds" list and the
- * "pending_redispatches" list.
- */
-
-struct _FlKeyboardPendingEvent {
-  GObject parent_instance;
-
-  // The target event.
-  //
-  // This is freed by #FlKeyboardPendingEvent if not null.
-  std::unique_ptr<FlKeyEvent> event;
-
-  // Self-incrementing ID attached to an event sent to the framework.
-  //
-  // Used to identify pending responds.
-  uint64_t sequence_id;
-  // The number of responders that haven't replied.
-  size_t unreplied;
-  // Whether any replied responders reported true (handled).
-  bool any_handled;
-
-  // A value calculated out of critical event information that can be used
-  // to identify redispatched events.
-  uint64_t hash;
-};
-
-G_DEFINE_TYPE(FlKeyboardPendingEvent, fl_keyboard_pending_event, G_TYPE_OBJECT)
-
-static void fl_keyboard_pending_event_dispose(GObject* object) {
-  // Redundant, but added so that we don't get a warning about unused function
-  // for FL_IS_KEYBOARD_PENDING_EVENT.
-  g_return_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(object));
-
-  FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT(object);
-  if (self->event != nullptr) {
-    fl_key_event_dispose(self->event.release());
-  }
-  G_OBJECT_CLASS(fl_keyboard_pending_event_parent_class)->dispose(object);
-}
-
-static void fl_keyboard_pending_event_class_init(
-    FlKeyboardPendingEventClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_pending_event_dispose;
-}
-
-static void fl_keyboard_pending_event_init(FlKeyboardPendingEvent* self) {}
-
-// Calculates a unique ID for a given FlKeyEvent object to use for
-// identification of responses from the framework.
-static uint64_t fl_keyboard_handler_get_event_hash(FlKeyEvent* event) {
-  // Combine the event timestamp, the type of event, and the hardware keycode
-  // (scan code) of the event to come up with a unique id for this event that
-  // can be derived solely from the event data itself, so that we can identify
-  // whether or not we have seen this event already.
-  guint64 type =
-      static_cast<uint64_t>(event->is_press ? GDK_KEY_PRESS : GDK_KEY_RELEASE);
-  guint64 keycode = static_cast<uint64_t>(event->keycode);
-  return (event->time & 0xffffffff) | ((type & 0xffff) << 32) |
-         ((keycode & 0xffff) << 48);
-}
-
-// Create a new FlKeyboardPendingEvent by providing the target event,
-// the sequence ID, and the number of responders that will reply.
-//
-// This will acquire the ownership of the event.
-static FlKeyboardPendingEvent* fl_keyboard_pending_event_new(
-    std::unique_ptr<FlKeyEvent> event,
-    uint64_t sequence_id,
-    size_t to_reply) {
-  FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT(
-      g_object_new(fl_keyboard_pending_event_get_type(), nullptr));
-
-  self->event = std::move(event);
-  self->sequence_id = sequence_id;
-  self->unreplied = to_reply;
-  self->any_handled = false;
-  self->hash = fl_keyboard_handler_get_event_hash(self->event.get());
-  return self;
-}
-
 /* Define FlKeyboardHandlerUserData */
 
 /**
@@ -299,60 +207,6 @@
 
 G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT);
 
-static void fl_keyboard_handler_dispose(GObject* object);
-
-static void fl_keyboard_handler_class_init(FlKeyboardHandlerClass* klass) {
-  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose;
-}
-
-static void fl_keyboard_handler_init(FlKeyboardHandler* self) {
-  self->derived_layout = std::make_unique<DerivedLayout>();
-
-  self->keycode_to_goals =
-      std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
-  self->logical_to_mandatory_goals =
-      std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
-  for (const LayoutGoal& goal : layout_goals) {
-    (*self->keycode_to_goals)[goal.keycode] = &goal;
-    if (goal.mandatory) {
-      (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
-    }
-  }
-
-  self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
-
-  self->pending_responds = g_ptr_array_new();
-  self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
-
-  self->last_sequence_id = 1;
-}
-
-static void fl_keyboard_handler_dispose(GObject* object) {
-  FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(object);
-
-  if (self->view_delegate != nullptr) {
-    fl_keyboard_view_delegate_subscribe_to_layout_change(self->view_delegate,
-                                                         nullptr);
-    g_object_remove_weak_pointer(
-        G_OBJECT(self->view_delegate),
-        reinterpret_cast<gpointer*>(&(self->view_delegate)));
-    self->view_delegate = nullptr;
-  }
-
-  self->derived_layout.reset();
-  self->keycode_to_goals.reset();
-  self->logical_to_mandatory_goals.reset();
-
-  g_ptr_array_free(self->responder_list, TRUE);
-  g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
-  g_ptr_array_free(self->pending_responds, TRUE);
-  g_ptr_array_free(self->pending_redispatches, TRUE);
-
-  G_OBJECT_CLASS(fl_keyboard_handler_parent_class)->dispose(object);
-}
-
-/* Implement FlKeyboardHandler */
-
 // This is an exact copy of g_ptr_array_find_with_equal_func.  Somehow CI
 // reports that can not find symbol g_ptr_array_find_with_equal_func, despite
 // the fact that it runs well locally.
@@ -377,22 +231,21 @@
   return FALSE;
 }
 
-// Compare a #FlKeyboardPendingEvent with the given sequence_id. The needle
-// should be a pointer to uint64_t sequence_id.
-static gboolean compare_pending_by_sequence_id(
-    gconstpointer pending,
-    gconstpointer needle_sequence_id) {
-  uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(needle_sequence_id);
-  return static_cast<const FlKeyboardPendingEvent*>(pending)->sequence_id ==
-         sequence_id;
+// Compare a #FlKeyboardPendingEvent with the given sequence_id.
+static gboolean compare_pending_by_sequence_id(gconstpointer a,
+                                               gconstpointer b) {
+  FlKeyboardPendingEvent* pending =
+      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+  uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
+  return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
 }
 
-// Compare a #FlKeyboardPendingEvent with the given hash. The #needle should be
-// a pointer to uint64_t hash.
-static gboolean compare_pending_by_hash(gconstpointer pending,
-                                        gconstpointer needle_hash) {
-  uint64_t hash = *reinterpret_cast<const uint64_t*>(needle_hash);
-  return static_cast<const FlKeyboardPendingEvent*>(pending)->hash == hash;
+// Compare a #FlKeyboardPendingEvent with the given hash.
+static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
+  FlKeyboardPendingEvent* pending =
+      FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+  uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
+  return fl_keyboard_pending_event_get_hash(pending) == hash;
 }
 
 // Try to remove a pending event from `pending_redispatches` with the target
@@ -431,22 +284,22 @@
   FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT(
       g_ptr_array_index(self->pending_responds, result_index));
   g_return_if_fail(pending != nullptr);
-  g_return_if_fail(pending->unreplied > 0);
-  pending->unreplied -= 1;
-  pending->any_handled = pending->any_handled || handled;
+  fl_keyboard_pending_event_mark_replied(pending, handled);
   // All responders have replied.
-  if (pending->unreplied == 0) {
+  if (fl_keyboard_pending_event_is_complete(pending)) {
     g_object_unref(user_data_ptr);
     gpointer removed =
         g_ptr_array_remove_index_fast(self->pending_responds, result_index);
     g_return_if_fail(removed == pending);
-    bool should_redispatch = !pending->any_handled &&
-                             !fl_keyboard_view_delegate_text_filter_key_press(
-                                 self->view_delegate, pending->event.get());
+    bool should_redispatch =
+        !fl_keyboard_pending_event_get_any_handled(pending) &&
+        !fl_keyboard_view_delegate_text_filter_key_press(
+            self->view_delegate, fl_keyboard_pending_event_get_event(pending));
     if (should_redispatch) {
       g_ptr_array_add(self->pending_redispatches, pending);
-      fl_keyboard_view_delegate_redispatch_event(self->view_delegate,
-                                                 std::move(pending->event));
+      fl_keyboard_view_delegate_redispatch_event(
+          self->view_delegate,
+          FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending)));
     } else {
       g_object_unref(pending);
     }
@@ -466,11 +319,11 @@
 // Make sure that Flutter has derived the layout for the group of the event,
 // if the event contains a goal keycode.
 static void guarantee_layout(FlKeyboardHandler* self, FlKeyEvent* event) {
-  guint8 group = event->group;
+  guint8 group = fl_key_event_get_group(event);
   if (self->derived_layout->find(group) != self->derived_layout->end()) {
     return;
   }
-  if (self->keycode_to_goals->find(event->keycode) ==
+  if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) ==
       self->keycode_to_goals->end()) {
     return;
   }
@@ -541,7 +394,7 @@
 }
 
 // Returns the keyboard pressed state.
-FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) {
+static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) {
   g_autoptr(FlValue) result = fl_value_new_map();
 
   GHashTable* pressing_records =
@@ -582,6 +435,67 @@
   }
 }
 
+// The loop body to dispatch an event to a responder.
+static void dispatch_to_responder(gpointer responder_data,
+                                  gpointer foreach_data_ptr) {
+  DispatchToResponderLoopContext* context =
+      reinterpret_cast<DispatchToResponderLoopContext*>(foreach_data_ptr);
+  FlKeyResponder* responder = FL_KEY_RESPONDER(responder_data);
+  fl_key_responder_handle_event(
+      responder, context->event, responder_handle_event_callback,
+      context->user_data, context->specified_logical_key);
+}
+
+static void fl_keyboard_handler_dispose(GObject* object) {
+  FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(object);
+
+  if (self->view_delegate != nullptr) {
+    fl_keyboard_view_delegate_subscribe_to_layout_change(self->view_delegate,
+                                                         nullptr);
+    g_object_remove_weak_pointer(
+        G_OBJECT(self->view_delegate),
+        reinterpret_cast<gpointer*>(&(self->view_delegate)));
+    self->view_delegate = nullptr;
+  }
+
+  self->derived_layout.reset();
+  self->keycode_to_goals.reset();
+  self->logical_to_mandatory_goals.reset();
+
+  g_ptr_array_free(self->responder_list, TRUE);
+  g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
+  g_ptr_array_free(self->pending_responds, TRUE);
+  g_ptr_array_free(self->pending_redispatches, TRUE);
+
+  G_OBJECT_CLASS(fl_keyboard_handler_parent_class)->dispose(object);
+}
+
+static void fl_keyboard_handler_class_init(FlKeyboardHandlerClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose;
+}
+
+static void fl_keyboard_handler_init(FlKeyboardHandler* self) {
+  self->derived_layout = std::make_unique<DerivedLayout>();
+
+  self->keycode_to_goals =
+      std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
+  self->logical_to_mandatory_goals =
+      std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
+  for (const LayoutGoal& goal : layout_goals) {
+    (*self->keycode_to_goals)[goal.keycode] = &goal;
+    if (goal.mandatory) {
+      (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
+    }
+  }
+
+  self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
+
+  self->pending_responds = g_ptr_array_new();
+  self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
+
+  self->last_sequence_id = 1;
+}
+
 FlKeyboardHandler* fl_keyboard_handler_new(
     FlBinaryMessenger* messenger,
     FlKeyboardViewDelegate* view_delegate) {
@@ -624,17 +538,6 @@
   return self;
 }
 
-// The loop body to dispatch an event to a responder.
-static void dispatch_to_responder(gpointer responder_data,
-                                  gpointer foreach_data_ptr) {
-  DispatchToResponderLoopContext* context =
-      reinterpret_cast<DispatchToResponderLoopContext*>(foreach_data_ptr);
-  FlKeyResponder* responder = FL_KEY_RESPONDER(responder_data);
-  fl_key_responder_handle_event(
-      responder, context->event, responder_handle_event_callback,
-      context->user_data, context->specified_logical_key);
-}
-
 gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* self,
                                           FlKeyEvent* event) {
   g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE);
@@ -643,18 +546,17 @@
 
   guarantee_layout(self, event);
 
-  uint64_t incoming_hash = fl_keyboard_handler_get_event_hash(event);
+  uint64_t incoming_hash = fl_key_event_hash(event);
   if (fl_keyboard_handler_remove_redispatched(self, incoming_hash)) {
     return FALSE;
   }
 
   FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new(
-      std::unique_ptr<FlKeyEvent>(event), ++self->last_sequence_id,
-      self->responder_list->len);
+      event, ++self->last_sequence_id, self->responder_list->len);
 
   g_ptr_array_add(self->pending_responds, pending);
-  FlKeyboardHandlerUserData* user_data =
-      fl_keyboard_handler_user_data_new(self, pending->sequence_id);
+  FlKeyboardHandlerUserData* user_data = fl_keyboard_handler_user_data_new(
+      self, fl_keyboard_pending_event_get_sequence_id(pending));
   DispatchToResponderLoopContext data{
       .event = event,
       .specified_logical_key =
diff --git a/shell/platform/linux/fl_keyboard_handler_test.cc b/shell/platform/linux/fl_keyboard_handler_test.cc
index 6acb2ab..194df9b 100644
--- a/shell/platform/linux/fl_keyboard_handler_test.cc
+++ b/shell/platform/linux/fl_keyboard_handler_test.cc
@@ -71,7 +71,7 @@
 typedef std::function<void(const FlutterKeyEvent* event,
                            AsyncKeyCallback callback)>
     EmbedderCallHandler;
-typedef std::function<void(std::unique_ptr<FlKeyEvent>)> RedispatchHandler;
+typedef std::function<void(FlKeyEvent*)> RedispatchHandler;
 
 // A type that can record all kinds of effects that the keyboard handler
 // triggers.
@@ -377,11 +377,11 @@
 
 static void fl_mock_view_keyboard_redispatch_event(
     FlKeyboardViewDelegate* view_delegate,
-    std::unique_ptr<FlKeyEvent> event) {
+    FlKeyEvent* event) {
   FlMockViewDelegatePrivate* priv =
       FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate);
   if (priv->redispatch_handler) {
-    priv->redispatch_handler(std::move(event));
+    priv->redispatch_handler(event);
   }
 }
 
@@ -467,23 +467,6 @@
 
 /***** End FlMockViewDelegate *****/
 
-// Create a new #FlKeyEvent with the given information.
-static FlKeyEvent* fl_key_event_new_by_mock(bool is_press,
-                                            guint keyval,
-                                            guint16 keycode,
-                                            GdkModifierType state,
-                                            gboolean is_modifier,
-                                            guint8 group = 0) {
-  FlKeyEvent* event = g_new0(FlKeyEvent, 1);
-  event->is_press = is_press;
-  event->time = 0;
-  event->state = state;
-  event->keyval = keyval;
-  event->group = group;
-  event->keycode = keycode;
-  return event;
-}
-
 class KeyboardTester {
  public:
   KeyboardTester() {
@@ -502,6 +485,7 @@
   ~KeyboardTester() {
     g_clear_object(&view_);
     g_clear_object(&handler_);
+    g_clear_pointer(&redispatched_events_, g_ptr_array_unref);
   }
 
   FlKeyboardHandler* handler() { return handler_; }
@@ -520,21 +504,20 @@
   // Returns the number of events redispatched. If any result is unexpected
   // (handled), return a minus number `-x` instead, where `x` is the index of
   // the first unexpected redispatch.
-  int redispatchEventsAndClear(
-      std::vector<std::unique_ptr<FlKeyEvent>>& events) {
-    size_t event_count = events.size();
+  int redispatchEventsAndClear(GPtrArray* events) {
+    guint event_count = events->len;
     int first_error = -1;
     during_redispatch_ = true;
-    for (size_t event_id = 0; event_id < event_count; event_id += 1) {
-      bool handled = fl_keyboard_handler_handle_event(
-          handler_, events[event_id].release());
+    for (guint event_id = 0; event_id < event_count; event_id += 1) {
+      FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id));
+      bool handled = fl_keyboard_handler_handle_event(handler_, event);
       EXPECT_FALSE(handled);
       if (handled) {
         first_error = first_error == -1 ? event_id : first_error;
       }
     }
     during_redispatch_ = false;
-    events.clear();
+    g_ptr_array_set_size(events, 0);
     return first_error < 0 ? event_count : -first_error;
   }
 
@@ -610,12 +593,11 @@
     fl_mock_view_set_text_filter_result(view_, response);
   }
 
-  void recordRedispatchedEventsTo(
-      std::vector<std::unique_ptr<FlKeyEvent>>& storage) {
-    fl_mock_view_set_redispatch_handler(
-        view_, [&storage](std::unique_ptr<FlKeyEvent> key) {
-          storage.push_back(std::move(key));
-        });
+  void recordRedispatchedEventsTo(GPtrArray* storage) {
+    redispatched_events_ = g_ptr_array_ref(storage);
+    fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) {
+      g_ptr_array_add(redispatched_events_, g_object_ref(key));
+    });
   }
 
   void setLayout(const MockLayoutData& layout) {
@@ -625,6 +607,7 @@
  private:
   FlMockViewDelegate* view_;
   FlKeyboardHandler* handler_;
+  GPtrArray* redispatched_events_ = nullptr;
   bool during_redispatch_ = false;
 
   static gboolean _flushChannelMessagesCb(gpointer data) {
@@ -642,16 +625,14 @@
 
   // Record calls so that they aren't responded.
   tester.recordEmbedderCallsTo(call_records);
-  fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_handler_handle_event(tester.handler(), event1);
 
   tester.respondToEmbedderCallsWith(true);
-  fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_handler_handle_event(tester.handler(), event2);
 
   tester.flushChannelMessages();
 
@@ -661,7 +642,8 @@
 TEST(FlKeyboardHandlerTest, SingleDelegateWithAsyncResponds) {
   KeyboardTester tester;
   std::vector<CallRecord> call_records;
-  std::vector<std::unique_ptr<FlKeyEvent>> redispatched;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
 
   gboolean handler_handled = false;
 
@@ -670,55 +652,56 @@
   tester.recordRedispatchedEventsTo(redispatched);
 
   // Dispatch a key event
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_EQ(call_records.size(), 1u);
   EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
                    kLogicalKeyA, "a", false);
 
   call_records[0].callback(true);
   tester.flushChannelMessages();
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
   call_records.clear();
 
   /// Test 2: Two events that are unhandled by the framework
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_EQ(call_records.size(), 1u);
   EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
                    kLogicalKeyA, nullptr, false);
 
   // Dispatch another key event
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_b, kKeyCodeKeyB,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event3 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event3);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_EQ(call_records.size(), 2u);
   EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB,
                    kLogicalKeyB, "b", false);
 
   // Resolve the second event first to test out-of-order response
   call_records[1].callback(false);
-  EXPECT_EQ(redispatched.size(), 1u);
-  EXPECT_EQ(redispatched[0]->keyval, 0x62u);
+  EXPECT_EQ(redispatched->len, 1u);
+  EXPECT_EQ(
+      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))),
+      0x62u);
   call_records[0].callback(false);
   tester.flushChannelMessages();
-  EXPECT_EQ(redispatched.size(), 2u);
-  EXPECT_EQ(redispatched[1]->keyval, 0x61u);
+  EXPECT_EQ(redispatched->len, 2u);
+  EXPECT_EQ(
+      fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))),
+      0x61u);
 
   EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler()));
   call_records.clear();
@@ -731,13 +714,12 @@
 
   /// Test 3: Dispatch the same event again to ensure that prevention from
   /// redispatching only works once.
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event4 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event4);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_EQ(call_records.size(), 1u);
 
   call_records[0].callback(true);
@@ -748,40 +730,39 @@
   KeyboardTester tester;
   gboolean handler_handled = false;
   std::vector<CallRecord> call_records;
-  std::vector<std::unique_ptr<FlKeyEvent>> redispatched;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
 
   /// Test 1: One event that is handled by the framework
   tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records);
   tester.recordRedispatchedEventsTo(redispatched);
 
   // Dispatch a key event
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
   EXPECT_EQ(call_records.size(), 1u);
   EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
                    kLogicalKeyA, "a", false);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   call_records.clear();
 
   EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-  redispatched.clear();
+  g_ptr_array_set_size(redispatched, 0);
 
   /// Test 2: An event unhandled by the framework
   tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records);
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
   EXPECT_EQ(call_records.size(), 1u);
   EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
                    kLogicalKeyA, nullptr, false);
-  EXPECT_EQ(redispatched.size(), 1u);
+  EXPECT_EQ(redispatched->len, 1u);
   call_records.clear();
 
   EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler()));
@@ -795,7 +776,8 @@
 TEST(FlKeyboardHandlerTest, WithTwoAsyncDelegates) {
   KeyboardTester tester;
   std::vector<CallRecord> call_records;
-  std::vector<std::unique_ptr<FlKeyEvent>> redispatched;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
 
   gboolean handler_handled = false;
 
@@ -805,13 +787,12 @@
 
   /// Test 1: One delegate responds true, the other false
 
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
 
   EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_EQ(call_records.size(), 2u);
 
   EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
@@ -820,19 +801,18 @@
   call_records[0].callback(true);
   call_records[1].callback(false);
   tester.flushChannelMessages();
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
 
   EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
   call_records.clear();
 
   /// Test 2: All delegates respond false
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+      0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
 
   EXPECT_EQ(handler_handled, true);
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
   EXPECT_EQ(call_records.size(), 2u);
 
   EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder);
@@ -845,7 +825,7 @@
 
   // Resolve redispatch
   tester.flushChannelMessages();
-  EXPECT_EQ(redispatched.size(), 1u);
+  EXPECT_EQ(redispatched->len, 1u);
   EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
   EXPECT_EQ(call_records.size(), 0u);
 
@@ -854,20 +834,20 @@
 
 TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsFalse) {
   KeyboardTester tester;
-  std::vector<std::unique_ptr<FlKeyEvent>> redispatched;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
   gboolean handler_handled = false;
   tester.recordRedispatchedEventsTo(redispatched);
   tester.respondToTextInputWith(false);
 
   // Dispatch a key event.
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
   // The event was redispatched because no one handles it.
-  EXPECT_EQ(redispatched.size(), 1u);
+  EXPECT_EQ(redispatched->len, 1u);
 
   // Resolve redispatched event.
   EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1);
@@ -877,20 +857,20 @@
 
 TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsTrue) {
   KeyboardTester tester;
-  std::vector<std::unique_ptr<FlKeyEvent>> redispatched;
+  g_autoptr(GPtrArray) redispatched =
+      g_ptr_array_new_with_free_func(g_object_unref);
   gboolean handler_handled = false;
   tester.recordRedispatchedEventsTo(redispatched);
   tester.respondToTextInputWith(true);
 
   // Dispatch a key event.
-  handler_handled = fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
   tester.flushChannelMessages();
   EXPECT_EQ(handler_handled, true);
   // The event was not redispatched because handler handles it.
-  EXPECT_EQ(redispatched.size(), 0u);
+  EXPECT_EQ(redispatched->len, 0u);
 
   EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
 }
@@ -902,14 +882,12 @@
   tester.recordEmbedderCallsTo(call_records);
 
   auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) {
-    fl_keyboard_handler_handle_event(
-        tester.handler(), fl_key_event_new_by_mock(
-                              true, keyval, keycode,
-                              static_cast<GdkModifierType>(0), false, group));
-    fl_keyboard_handler_handle_event(
-        tester.handler(), fl_key_event_new_by_mock(
-                              false, keyval, keycode,
-                              static_cast<GdkModifierType>(0), false, group));
+    g_autoptr(FlKeyEvent) event1 = fl_key_event_new(
+        0, TRUE, keycode, keyval, static_cast<GdkModifierType>(0), group);
+    fl_keyboard_handler_handle_event(tester.handler(), event1);
+    g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+        0, FALSE, keycode, keyval, static_cast<GdkModifierType>(0), group);
+    fl_keyboard_handler_handle_event(tester.handler(), event2);
   };
 
   /* US keyboard layout */
@@ -1033,10 +1011,9 @@
   tester.respondToTextInputWith(true);
 
   // Dispatch a key event.
-  fl_keyboard_handler_handle_event(
-      tester.handler(),
-      fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA,
-                               static_cast<GdkModifierType>(0), false));
+  g_autoptr(FlKeyEvent) event = fl_key_event_new(
+      0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast<GdkModifierType>(0), 0);
+  fl_keyboard_handler_handle_event(tester.handler(), event);
 
   GHashTable* pressedState =
       fl_keyboard_handler_get_pressed_state(tester.handler());
diff --git a/shell/platform/linux/fl_keyboard_pending_event.cc b/shell/platform/linux/fl_keyboard_pending_event.cc
new file mode 100644
index 0000000..eb9b6c3
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_pending_event.cc
@@ -0,0 +1,105 @@
+// Copyright 2013 The Flutter 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 "flutter/shell/platform/linux/fl_keyboard_pending_event.h"
+
+/**
+ * FlKeyboardPendingEvent:
+ * A record for events that have been received by the handler, but
+ * dispatched to other objects, whose results have yet to return.
+ *
+ * This object is used by both the "pending_responds" list and the
+ * "pending_redispatches" list.
+ */
+
+struct _FlKeyboardPendingEvent {
+  GObject parent_instance;
+
+  // The target event.
+  FlKeyEvent* event;
+
+  // Unique ID to identify pending responds.
+  uint64_t sequence_id;
+
+  // The number of responders that haven't replied.
+  size_t unreplied;
+
+  // Whether any replied responders reported true (handled).
+  bool any_handled;
+
+  // A value calculated out of critical event information that can be used
+  // to identify redispatched events.
+  uint64_t hash;
+};
+
+G_DEFINE_TYPE(FlKeyboardPendingEvent, fl_keyboard_pending_event, G_TYPE_OBJECT)
+
+static void fl_keyboard_pending_event_dispose(GObject* object) {
+  FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT(object);
+
+  g_clear_object(&self->event);
+
+  G_OBJECT_CLASS(fl_keyboard_pending_event_parent_class)->dispose(object);
+}
+
+static void fl_keyboard_pending_event_class_init(
+    FlKeyboardPendingEventClass* klass) {
+  G_OBJECT_CLASS(klass)->dispose = fl_keyboard_pending_event_dispose;
+}
+
+static void fl_keyboard_pending_event_init(FlKeyboardPendingEvent* self) {}
+
+// Creates a new FlKeyboardPendingEvent by providing the target event,
+// the sequence ID, and the number of responders that will reply.
+FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event,
+                                                      uint64_t sequence_id,
+                                                      size_t to_reply) {
+  FlKeyboardPendingEvent* self = FL_KEYBOARD_PENDING_EVENT(
+      g_object_new(fl_keyboard_pending_event_get_type(), nullptr));
+
+  self->event = FL_KEY_EVENT(g_object_ref(event));
+  self->sequence_id = sequence_id;
+  self->unreplied = to_reply;
+  self->any_handled = false;
+  self->hash = fl_key_event_hash(self->event);
+
+  return self;
+}
+
+FlKeyEvent* fl_keyboard_pending_event_get_event(FlKeyboardPendingEvent* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), nullptr);
+  return self->event;
+}
+
+uint64_t fl_keyboard_pending_event_get_sequence_id(
+    FlKeyboardPendingEvent* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), 0);
+  return self->sequence_id;
+}
+
+uint64_t fl_keyboard_pending_event_get_hash(FlKeyboardPendingEvent* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), 0);
+  return self->hash;
+}
+
+void fl_keyboard_pending_event_mark_replied(FlKeyboardPendingEvent* self,
+                                            gboolean handled) {
+  g_return_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self));
+  g_return_if_fail(self->unreplied > 0);
+  self->unreplied -= 1;
+  if (handled) {
+    self->any_handled = TRUE;
+  }
+}
+
+gboolean fl_keyboard_pending_event_get_any_handled(
+    FlKeyboardPendingEvent* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), FALSE);
+  return self->any_handled;
+}
+
+gboolean fl_keyboard_pending_event_is_complete(FlKeyboardPendingEvent* self) {
+  g_return_val_if_fail(FL_IS_KEYBOARD_PENDING_EVENT(self), FALSE);
+  return self->unreplied == 0;
+}
diff --git a/shell/platform/linux/fl_keyboard_pending_event.h b/shell/platform/linux/fl_keyboard_pending_event.h
new file mode 100644
index 0000000..50204d6
--- /dev/null
+++ b/shell/platform/linux/fl_keyboard_pending_event.h
@@ -0,0 +1,39 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_PENDING_EVENT_H_
+#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_PENDING_EVENT_H_
+
+#include "fl_key_event.h"
+
+G_BEGIN_DECLS
+
+G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
+                     fl_keyboard_pending_event,
+                     FL,
+                     KEYBOARD_PENDING_EVENT,
+                     GObject);
+
+FlKeyboardPendingEvent* fl_keyboard_pending_event_new(FlKeyEvent* event,
+                                                      uint64_t sequence_id,
+                                                      size_t to_reply);
+
+FlKeyEvent* fl_keyboard_pending_event_get_event(FlKeyboardPendingEvent* event);
+
+uint64_t fl_keyboard_pending_event_get_sequence_id(
+    FlKeyboardPendingEvent* event);
+
+uint64_t fl_keyboard_pending_event_get_hash(FlKeyboardPendingEvent* event);
+
+void fl_keyboard_pending_event_mark_replied(FlKeyboardPendingEvent* event,
+                                            gboolean handled);
+
+gboolean fl_keyboard_pending_event_get_any_handled(
+    FlKeyboardPendingEvent* event);
+
+gboolean fl_keyboard_pending_event_is_complete(FlKeyboardPendingEvent* event);
+
+G_END_DECLS
+
+#endif  // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_PENDING_EVENT_H_
diff --git a/shell/platform/linux/fl_keyboard_view_delegate.cc b/shell/platform/linux/fl_keyboard_view_delegate.cc
index 3c5fac9..e3c79c2 100644
--- a/shell/platform/linux/fl_keyboard_view_delegate.cc
+++ b/shell/platform/linux/fl_keyboard_view_delegate.cc
@@ -39,14 +39,13 @@
   return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->get_messenger(self);
 }
 
-void fl_keyboard_view_delegate_redispatch_event(
-    FlKeyboardViewDelegate* self,
-    std::unique_ptr<FlKeyEvent> event) {
+void fl_keyboard_view_delegate_redispatch_event(FlKeyboardViewDelegate* self,
+                                                FlKeyEvent* event) {
   g_return_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self));
   g_return_if_fail(event != nullptr);
 
-  return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->redispatch_event(
-      self, std::move(event));
+  return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->redispatch_event(self,
+                                                                     event);
 }
 
 void fl_keyboard_view_delegate_subscribe_to_layout_change(
diff --git a/shell/platform/linux/fl_keyboard_view_delegate.h b/shell/platform/linux/fl_keyboard_view_delegate.h
index 59fdc3f..a84cc58 100644
--- a/shell/platform/linux/fl_keyboard_view_delegate.h
+++ b/shell/platform/linux/fl_keyboard_view_delegate.h
@@ -46,8 +46,7 @@
 
   FlBinaryMessenger* (*get_messenger)(FlKeyboardViewDelegate* delegate);
 
-  void (*redispatch_event)(FlKeyboardViewDelegate* delegate,
-                           std::unique_ptr<FlKeyEvent> event);
+  void (*redispatch_event)(FlKeyboardViewDelegate* delegate, FlKeyEvent* event);
 
   void (*subscribe_to_layout_change)(FlKeyboardViewDelegate* delegate,
                                      KeyboardLayoutNotifier notifier);
@@ -103,13 +102,10 @@
  *
  * Handles `FlKeyboardHandler`'s request to insert a GDK event to the system for
  * redispatching.
- *
- * The ownership of event will be transferred to the view delegate. The view
- * delegate is responsible to call fl_key_event_dispose.
  */
 void fl_keyboard_view_delegate_redispatch_event(
     FlKeyboardViewDelegate* delegate,
-    std::unique_ptr<FlKeyEvent> event);
+    FlKeyEvent* event);
 
 void fl_keyboard_view_delegate_subscribe_to_layout_change(
     FlKeyboardViewDelegate* delegate,
diff --git a/shell/platform/linux/fl_text_input_handler.cc b/shell/platform/linux/fl_text_input_handler.cc
index afc8afc..f4d3eb7 100644
--- a/shell/platform/linux/fl_text_input_handler.cc
+++ b/shell/platform/linux/fl_text_input_handler.cc
@@ -627,7 +627,8 @@
   }
 
   if (gtk_im_context_filter_keypress(
-          priv->im_context, reinterpret_cast<GdkEventKey*>(event->origin))) {
+          priv->im_context,
+          reinterpret_cast<GdkEventKey*>(fl_key_event_get_origin(event)))) {
     return TRUE;
   }
 
@@ -639,11 +640,11 @@
   gboolean do_action = FALSE;
   // Handle navigation keys.
   gboolean changed = FALSE;
-  if (event->is_press) {
-    switch (event->keyval) {
+  if (fl_key_event_get_is_press(event)) {
+    switch (fl_key_event_get_keyval(event)) {
       case GDK_KEY_End:
       case GDK_KEY_KP_End:
-        if (event->state & GDK_SHIFT_MASK) {
+        if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) {
           changed = priv->text_model->SelectToEnd();
         } else {
           changed = priv->text_model->MoveCursorToEnd();
@@ -662,7 +663,7 @@
         break;
       case GDK_KEY_Home:
       case GDK_KEY_KP_Home:
-        if (event->state & GDK_SHIFT_MASK) {
+        if (fl_key_event_get_state(event) & GDK_SHIFT_MASK) {
           changed = priv->text_model->SelectToBeginning();
         } else {
           changed = priv->text_model->MoveCursorToBeginning();
diff --git a/shell/platform/linux/fl_text_input_handler_test.cc b/shell/platform/linux/fl_text_input_handler_test.cc
index 4c4606a..7787b42 100644
--- a/shell/platform/linux/fl_text_input_handler_test.cc
+++ b/shell/platform/linux/fl_text_input_handler_test.cc
@@ -184,9 +184,8 @@
   GdkEvent* gdk_event = gdk_event_new(GDK_KEY_PRESS);
   gdk_event->key.keyval = keyval;
   gdk_event->key.state = state;
-  FlKeyEvent* key_event = fl_key_event_new_from_gdk_event(gdk_event);
+  g_autoptr(FlKeyEvent) key_event = fl_key_event_new_from_gdk_event(gdk_event);
   fl_text_input_handler_filter_keypress(handler, key_event);
-  fl_key_event_dispose(key_event);
 }
 
 TEST(FlTextInputHandlerTest, MessageHandler) {
diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc
index bfaedc5..e84b144 100644
--- a/shell/platform/linux/fl_view.cc
+++ b/shell/platform/linux/fl_view.cc
@@ -357,13 +357,12 @@
   };
 
   iface->redispatch_event = [](FlKeyboardViewDelegate* view_delegate,
-                               std::unique_ptr<FlKeyEvent> in_event) {
-    FlKeyEvent* event = in_event.release();
-    GdkEventType event_type = gdk_event_get_event_type(event->origin);
+                               FlKeyEvent* event) {
+    GdkEventType event_type =
+        gdk_event_get_event_type(fl_key_event_get_origin(event));
     g_return_if_fail(event_type == GDK_KEY_PRESS ||
                      event_type == GDK_KEY_RELEASE);
-    gdk_event_put(event->origin);
-    fl_key_event_dispose(event);
+    gdk_event_put(fl_key_event_get_origin(event));
   };
 
   iface->subscribe_to_layout_change = [](FlKeyboardViewDelegate* view_delegate,