From 0f4fccfac4667d5f557e5183480d5c4fdf9d4217 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 10 Dec 2024 11:05:31 +1300 Subject: [PATCH] Make fl_engine_send_key_event into a standard async function. Add missing tests for this function. Note this makes FlKeyboardManager a bit more complex, but this is planned to be simplified in a future refactor. --- shell/platform/linux/fl_engine.cc | 40 +++++- shell/platform/linux/fl_engine_private.h | 30 ++++- shell/platform/linux/fl_engine_test.cc | 128 ++++++++++++++++++++ shell/platform/linux/fl_keyboard_manager.cc | 32 ++++- 4 files changed, 223 insertions(+), 7 deletions(-) diff --git a/shell/platform/linux/fl_engine.cc b/shell/platform/linux/fl_engine.cc index 0cc9497102e37..febf6284f2b06 100644 --- a/shell/platform/linux/fl_engine.cc +++ b/shell/platform/linux/fl_engine.cc @@ -965,17 +965,51 @@ void fl_engine_send_pointer_pan_zoom_event(FlEngine* self, self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1); } +static void send_key_event_cb(bool handled, void* user_data) { + g_autoptr(GTask) task = G_TASK(user_data); + gboolean* return_value = g_new0(gboolean, 1); + *return_value = handled; + g_task_return_pointer(task, return_value, g_free); +} + void fl_engine_send_key_event(FlEngine* self, const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data) { + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { g_return_if_fail(FL_IS_ENGINE(self)); + g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data); + if (self->engine == nullptr) { + g_task_return_new_error(task, fl_engine_error_quark(), + FL_ENGINE_ERROR_FAILED, "No engine"); return; } - self->embedder_api.SendKeyEvent(self->engine, event, callback, user_data); + if (self->embedder_api.SendKeyEvent(self->engine, event, send_key_event_cb, + g_object_ref(task)) != kSuccess) { + g_task_return_new_error(task, fl_engine_error_quark(), + FL_ENGINE_ERROR_FAILED, "Failed to send key event"); + g_object_unref(task); + } +} + +gboolean fl_engine_send_key_event_finish(FlEngine* self, + GAsyncResult* result, + gboolean* handled, + GError** error) { + g_return_val_if_fail(FL_IS_ENGINE(self), FALSE); + g_return_val_if_fail(g_task_is_valid(result, self), FALSE); + + g_autofree gboolean* return_value = + static_cast(g_task_propagate_pointer(G_TASK(result), error)); + if (return_value == nullptr) { + return FALSE; + } + + *handled = *return_value; + return TRUE; } void fl_engine_dispatch_semantics_action(FlEngine* self, diff --git a/shell/platform/linux/fl_engine_private.h b/shell/platform/linux/fl_engine_private.h index b6fce3056d9e7..f97086dd78a41 100644 --- a/shell/platform/linux/fl_engine_private.h +++ b/shell/platform/linux/fl_engine_private.h @@ -280,11 +280,37 @@ void fl_engine_send_pointer_pan_zoom_event(FlEngine* engine, /** * fl_engine_send_key_event: + * @engine: an #FlEngine. + * @event: key event to send. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the request is + * satisfied. + * @user_data: (closure): user data to pass to @callback. + * + * Send a key event to the engine. */ void fl_engine_send_key_event(FlEngine* engine, const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data); + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_engine_send_key_event_finish: + * @engine: an #FlEngine. + * @result: a #GAsyncResult. + * @handled: location to write if this event was handled by the engine. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_engine_send_key_event(). + * + * Returns: %TRUE on success. + */ +gboolean fl_engine_send_key_event_finish(FlEngine* engine, + GAsyncResult* result, + gboolean* handled, + GError** error); /** * fl_engine_dispatch_semantics_action: diff --git a/shell/platform/linux/fl_engine_test.cc b/shell/platform/linux/fl_engine_test.cc index 62fcb48974f15..083b64930138e 100644 --- a/shell/platform/linux/fl_engine_test.cc +++ b/shell/platform/linux/fl_engine_test.cc @@ -748,4 +748,132 @@ TEST(FlEngineTest, RemoveViewEngineError) { g_main_loop_run(loop); } +TEST(FlEngineTest, SendKeyEvent) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called; + embedder_api->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&called](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + called = true; + EXPECT_EQ(event->timestamp, 1234); + EXPECT_EQ(event->type, kFlutterKeyEventTypeUp); + EXPECT_EQ(event->physical, static_cast(42)); + EXPECT_EQ(event->logical, static_cast(123)); + EXPECT_TRUE(event->synthesized); + EXPECT_EQ(event->device_type, kFlutterKeyEventDeviceTypeKeyboard); + callback(TRUE, user_data); + return kSuccess; + })); + + FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent), + .timestamp = 1234, + .type = kFlutterKeyEventTypeUp, + .physical = 42, + .logical = 123, + .character = nullptr, + .synthesized = true, + .device_type = kFlutterKeyEventDeviceTypeKeyboard}; + fl_engine_send_key_event( + engine, &event, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_send_key_event_finish(FL_ENGINE(object), result, + &handled, &error)); + EXPECT_EQ(error, nullptr); + EXPECT_TRUE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); + + g_main_loop_run(loop); + EXPECT_TRUE(called); +} + +TEST(FlEngineTest, SendKeyEventNotHandled) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called; + embedder_api->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&called](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + called = true; + callback(FALSE, user_data); + return kSuccess; + })); + + FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent), + .timestamp = 1234, + .type = kFlutterKeyEventTypeUp, + .physical = 42, + .logical = 123, + .character = nullptr, + .synthesized = true, + .device_type = kFlutterKeyEventDeviceTypeKeyboard}; + fl_engine_send_key_event( + engine, &event, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + g_autoptr(GError) error = nullptr; + EXPECT_TRUE(fl_engine_send_key_event_finish(FL_ENGINE(object), result, + &handled, &error)); + EXPECT_EQ(error, nullptr); + EXPECT_FALSE(handled); + g_main_loop_quit(static_cast(user_data)); + }, + loop); + + g_main_loop_run(loop); + EXPECT_TRUE(called); +} + +TEST(FlEngineTest, SendKeyEventError) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + FlutterEngineProcTable* embedder_api = fl_engine_get_embedder_api(engine); + + bool called; + embedder_api->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&called](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + called = true; + return kInvalidArguments; + })); + + FlutterKeyEvent event = {.struct_size = sizeof(FlutterKeyEvent), + .timestamp = 1234, + .type = kFlutterKeyEventTypeUp, + .physical = 42, + .logical = 123, + .character = nullptr, + .synthesized = true, + .device_type = kFlutterKeyEventDeviceTypeKeyboard}; + fl_engine_send_key_event( + engine, &event, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + g_autoptr(GError) error = nullptr; + EXPECT_FALSE(fl_engine_send_key_event_finish(FL_ENGINE(object), result, + &handled, &error)); + EXPECT_NE(error, nullptr); + EXPECT_STREQ(error->message, "Failed to send key event"); + g_main_loop_quit(static_cast(user_data)); + }, + loop); + + g_main_loop_run(loop); + EXPECT_TRUE(called); +} + // NOLINTEND(clang-analyzer-core.StackAddressEscape) diff --git a/shell/platform/linux/fl_keyboard_manager.cc b/shell/platform/linux/fl_keyboard_manager.cc index 0a4925737a750..3e84c736a3e07 100644 --- a/shell/platform/linux/fl_keyboard_manager.cc +++ b/shell/platform/linux/fl_keyboard_manager.cc @@ -9,6 +9,7 @@ #include #include +#include "flutter/shell/platform/linux/fl_engine_private.h" #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_layout.h" @@ -479,8 +480,35 @@ FlKeyboardManager* fl_keyboard_manager_new( } else { g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); if (engine != nullptr) { - fl_engine_send_key_event(engine, event, callback, - callback_user_data); + typedef struct { + FlutterKeyEventCallback callback; + void* callback_user_data; + } SendKeyEventData; + SendKeyEventData* data = g_new0(SendKeyEventData, 1); + data->callback = callback; + data->callback_user_data = callback_user_data; + fl_engine_send_key_event( + engine, event, self->cancellable, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + g_autofree SendKeyEventData* data = + static_cast(user_data); + gboolean handled = FALSE; + g_autoptr(GError) error = nullptr; + if (!fl_engine_send_key_event_finish( + FL_ENGINE(object), result, &handled, &error)) { + if (g_error_matches(error, G_IO_ERROR, + G_IO_ERROR_CANCELLED)) { + return; + } + + g_warning("Failed to send key event: %s", error->message); + } + + if (data->callback != nullptr) { + data->callback(handled, data->callback_user_data); + } + }, + data); } } },