diff --git a/engine/src/flutter/shell/platform/linux/fl_engine.cc b/engine/src/flutter/shell/platform/linux/fl_engine.cc index 0b7727a39f..e871d29af7 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine.cc +++ b/engine/src/flutter/shell/platform/linux/fl_engine.cc @@ -545,6 +545,15 @@ G_MODULE_EXPORT FlEngine* fl_engine_new(FlDartProject* project) { return fl_engine_new_with_renderer(project, FL_RENDERER(renderer)); } +FlEngine* fl_engine_new_with_binary_messenger( + FlBinaryMessenger* binary_messenger) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + FlEngine* self = fl_engine_new(project); + g_object_unref(self->binary_messenger); + self->binary_messenger = FL_BINARY_MESSENGER(g_object_ref(binary_messenger)); + return self; +} + G_MODULE_EXPORT FlEngine* fl_engine_new_headless(FlDartProject* project) { g_autoptr(FlRendererHeadless) renderer = fl_renderer_headless_new(); return fl_engine_new_with_renderer(project, FL_RENDERER(renderer)); diff --git a/engine/src/flutter/shell/platform/linux/fl_engine_private.h b/engine/src/flutter/shell/platform/linux/fl_engine_private.h index 3a4b1ef174..069fc9217d 100644 --- a/engine/src/flutter/shell/platform/linux/fl_engine_private.h +++ b/engine/src/flutter/shell/platform/linux/fl_engine_private.h @@ -61,6 +61,17 @@ typedef void (*FlEngineUpdateSemanticsHandler)( const FlutterSemanticsUpdate2* update, gpointer user_data); +/** + * fl_engine_new_with_binary_messenger: + * @binary_messenger: an #FlBinaryMessenger. + * + * Creates a new engine with a custom binary messenger. Used for testing. + * + * Returns: a new #FlEngine. + */ +FlEngine* fl_engine_new_with_binary_messenger( + FlBinaryMessenger* binary_messenger); + /** * fl_engine_new_with_renderer: * @project: an #FlDartProject. diff --git a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc index 1f714acdc5..ab9f80179e 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc @@ -73,55 +73,6 @@ static uint64_t to_lower(uint64_t n) { return n; } -/** - * FlKeyEmbedderUserData: - * The user_data used when #FlKeyEmbedderResponder sends message through the - * embedder.SendKeyEvent API. - */ -G_DECLARE_FINAL_TYPE(FlKeyEmbedderUserData, - fl_key_embedder_user_data, - FL, - KEY_EMBEDDER_USER_DATA, - GObject); - -struct _FlKeyEmbedderUserData { - GObject parent_instance; - - FlKeyEmbedderResponderAsyncCallback callback; - gpointer user_data; -}; - -G_DEFINE_TYPE(FlKeyEmbedderUserData, fl_key_embedder_user_data, G_TYPE_OBJECT) - -static void fl_key_embedder_user_data_dispose(GObject* object); - -static void fl_key_embedder_user_data_class_init( - FlKeyEmbedderUserDataClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_key_embedder_user_data_dispose; -} - -static void fl_key_embedder_user_data_init(FlKeyEmbedderUserData* self) {} - -static void fl_key_embedder_user_data_dispose(GObject* object) { - // The following line suppresses a warning for unused function - // FL_IS_KEY_EMBEDDER_USER_DATA. - g_return_if_fail(FL_IS_KEY_EMBEDDER_USER_DATA(object)); -} - -// Creates a new FlKeyChannelUserData private class with all information. -// -// The callback and the user_data might be nullptr. -static FlKeyEmbedderUserData* fl_key_embedder_user_data_new( - FlKeyEmbedderResponderAsyncCallback callback, - gpointer user_data) { - FlKeyEmbedderUserData* self = FL_KEY_EMBEDDER_USER_DATA( - g_object_new(fl_key_embedder_user_data_get_type(), nullptr)); - - self->callback = callback; - self->user_data = user_data; - return self; -} - namespace { typedef enum { @@ -135,8 +86,8 @@ typedef enum { struct _FlKeyEmbedderResponder { GObject parent_instance; - EmbedderSendKeyEvent send_key_event; - void* send_key_event_user_data; + // Engine sending key events to. + GWeakRef engine; // Internal record for states of whether a key is pressed. // @@ -190,6 +141,8 @@ struct _FlKeyEmbedderResponder { // It is a map from primary physical keys to lock bits. Both keys and values // are directly stored uint64s. This table is freed by the responder. GHashTable* logical_key_to_lock_bit; + + GCancellable* cancellable; }; static void fl_key_embedder_responder_dispose(GObject* object); @@ -203,17 +156,23 @@ static void fl_key_embedder_responder_class_init( } // Initializes an FlKeyEmbedderResponder instance. -static void fl_key_embedder_responder_init(FlKeyEmbedderResponder* self) {} +static void fl_key_embedder_responder_init(FlKeyEmbedderResponder* self) { + self->cancellable = g_cancellable_new(); +} // Disposes of an FlKeyEmbedderResponder instance. static void fl_key_embedder_responder_dispose(GObject* object) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(object); + g_cancellable_cancel(self->cancellable); + + g_weak_ref_clear(&self->engine); g_clear_pointer(&self->pressing_records, g_hash_table_unref); g_clear_pointer(&self->mapping_records, g_hash_table_unref); g_clear_pointer(&self->modifier_bit_to_checked_keys, g_hash_table_unref); g_clear_pointer(&self->lock_bit_to_checked_keys, g_hash_table_unref); g_clear_pointer(&self->logical_key_to_lock_bit, g_hash_table_unref); + g_clear_object(&self->cancellable); G_OBJECT_CLASS(fl_key_embedder_responder_parent_class)->dispose(object); } @@ -234,14 +193,11 @@ static void initialize_logical_key_to_lock_bit_loop_body(gpointer lock_bit, } // Creates a new FlKeyEmbedderResponder instance. -FlKeyEmbedderResponder* fl_key_embedder_responder_new( - EmbedderSendKeyEvent send_key_event, - void* send_key_event_user_data) { +FlKeyEmbedderResponder* fl_key_embedder_responder_new(FlEngine* engine) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER( g_object_new(fl_key_embedder_responder_get_type(), nullptr)); - self->send_key_event = send_key_event; - self->send_key_event_user_data = send_key_event_user_data; + g_weak_ref_init(&self->engine, engine); self->pressing_records = g_hash_table_new(g_direct_hash, g_direct_equal); self->mapping_records = g_hash_table_new(g_direct_hash, g_direct_equal); @@ -311,16 +267,6 @@ static char* event_to_character(FlKeyEvent* event) { return result; } -// Handles a response from the embedder API to a key event sent to the framework -// earlier. -static void handle_response(bool handled, gpointer user_data) { - g_autoptr(FlKeyEmbedderUserData) data = FL_KEY_EMBEDDER_USER_DATA(user_data); - - g_return_if_fail(data->callback != nullptr); - - data->callback(handled, data->user_data); -} - // Sends a synthesized event to the framework with no demand for callback. static void synthesize_simple_event(FlKeyEmbedderResponder* self, FlutterKeyEventType type, @@ -336,8 +282,11 @@ static void synthesize_simple_event(FlKeyEmbedderResponder* self, out_event.character = nullptr; out_event.synthesized = true; self->sent_any_events = true; - self->send_key_event(&out_event, nullptr, nullptr, - self->send_key_event_user_data); + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine != nullptr) { + fl_engine_send_key_event(engine, &out_event, self->cancellable, nullptr, + nullptr); + } } namespace { @@ -750,13 +699,9 @@ static void fl_key_embedder_responder_handle_event_impl( FlKeyEmbedderResponder* responder, FlKeyEvent* event, uint64_t specified_logical_key, - FlKeyEmbedderResponderAsyncCallback callback, - gpointer user_data) { + GTask* task) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); - g_return_if_fail(event != nullptr); - g_return_if_fail(callback != nullptr); - const uint64_t logical_key = specified_logical_key != 0 ? specified_logical_key : event_to_logical_key(event); @@ -811,7 +756,9 @@ static void fl_key_embedder_responder_handle_event_impl( // The physical key has been released before. It might indicate a missed // event due to loss of focus, or multiple keyboards pressed keys with the // same physical key. Ignore the up event. - callback(true, user_data); + gboolean* return_value = g_new0(gboolean, 1); + *return_value = TRUE; + g_task_return_pointer(task, return_value, g_free); return; } else { out_event.type = kFlutterKeyEventTypeUp; @@ -825,41 +772,85 @@ static void fl_key_embedder_responder_handle_event_impl( if (is_down_event) { update_mapping_record(self, physical_key, logical_key); } - FlKeyEmbedderUserData* response_data = - fl_key_embedder_user_data_new(callback, user_data); self->sent_any_events = true; - self->send_key_event(&out_event, handle_response, response_data, - self->send_key_event_user_data); -} + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine != nullptr) { + fl_engine_send_key_event( + engine, &out_event, self->cancellable, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + g_autoptr(GTask) task = G_TASK(user_data); -void fl_key_embedder_responder_handle_event( - FlKeyEmbedderResponder* self, - FlKeyEvent* event, - uint64_t specified_logical_key, - FlKeyEmbedderResponderAsyncCallback callback, - gpointer user_data) { - self->sent_any_events = false; - fl_key_embedder_responder_handle_event_impl( - self, event, specified_logical_key, callback, user_data); - if (!self->sent_any_events) { - self->send_key_event(&kEmptyEvent, nullptr, nullptr, - self->send_key_event_user_data); + gboolean handled; + 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 handle key event: %s", error->message); + handled = FALSE; + } + + gboolean* return_value = g_new0(gboolean, 1); + *return_value = handled; + g_task_return_pointer(task, return_value, g_free); + }, + g_object_ref(task)); } } +void fl_key_embedder_responder_handle_event(FlKeyEmbedderResponder* self, + FlKeyEvent* event, + uint64_t specified_logical_key, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + g_autoptr(GTask) task = g_task_new(self, cancellable, callback, user_data); + + self->sent_any_events = false; + fl_key_embedder_responder_handle_event_impl(self, event, + specified_logical_key, task); + if (!self->sent_any_events) { + g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); + if (engine != nullptr) { + fl_engine_send_key_event(engine, &kEmptyEvent, self->cancellable, nullptr, + nullptr); + } + } +} + +gboolean fl_key_embedder_responder_handle_event_finish( + FlKeyEmbedderResponder* self, + GAsyncResult* result, + gboolean* handled, + GError** error) { + 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_key_embedder_responder_sync_modifiers_if_needed( - FlKeyEmbedderResponder* responder, + FlKeyEmbedderResponder* self, guint state, double event_time) { + g_return_if_fail(FL_IS_KEY_EMBEDDER_RESPONDER(self)); + const double timestamp = event_time * kMicrosecondsPerMillisecond; SyncStateLoopContext sync_state_context; - sync_state_context.self = responder; + sync_state_context.self = self; sync_state_context.state = state; sync_state_context.timestamp = timestamp; // Update pressing states. - g_hash_table_foreach(responder->modifier_bit_to_checked_keys, + g_hash_table_foreach(self->modifier_bit_to_checked_keys, synchronize_pressed_states_loop_body, &sync_state_context); } diff --git a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.h b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.h index 18e1f16ca5..5b27fee3c3 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.h +++ b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.h @@ -8,34 +8,6 @@ #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/fl_key_event.h" -// The signature of a function that FlKeyEmbedderResponder calls on every key -// event. -// -// The `user_data` is opaque data managed by the object that creates -// FlKeyEmbedderResponder, and is registered along with this callback -// via `fl_key_embedder_responder_new`. -// -// The `callback_user_data` is opaque data managed by FlKeyEmbedderResponder. -// Instances of the EmbedderSendKeyEvent callback are required to invoke -// `callback` with the `callback_user_data` parameter after the `event` has been -// processed. -typedef void (*EmbedderSendKeyEvent)(const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* callback_user_data, - void* send_key_event_user_data); - -/** - * FlKeyEmbedderResponderAsyncCallback: - * @event: whether the event has been handled. - * @user_data: the same value as user_data sent by - * #fl_key_responder_handle_event. - * - * The signature for a callback with which a #FlKeyEmbedderResponder - *asynchronously reports whether the responder handles the event. - **/ -typedef void (*FlKeyEmbedderResponderAsyncCallback)(bool handled, - gpointer user_data); - G_BEGIN_DECLS G_DECLARE_FINAL_TYPE(FlKeyEmbedderResponder, @@ -59,16 +31,10 @@ G_DECLARE_FINAL_TYPE(FlKeyEmbedderResponder, * the event. * * Creates a new #FlKeyEmbedderResponder. - * @send_key_event: a function that is called on every key event. - * @send_key_event_user_data: an opaque pointer that will be sent back as the - * last argument of send_key_event, created and managed by the object that holds - * FlKeyEmbedderResponder. * * Returns: a new #FlKeyEmbedderResponder. */ -FlKeyEmbedderResponder* fl_key_embedder_responder_new( - EmbedderSendKeyEvent send_key_event, - void* send_key_event_user_data); +FlKeyEmbedderResponder* fl_key_embedder_responder_new(FlEngine* engine); /** * fl_key_embedder_responder_handle_event: @@ -76,21 +42,38 @@ FlKeyEmbedderResponder* fl_key_embedder_responder_new( * @event: the event to be handled. Must not be null. The object is managed by * callee and must not be assumed available after this function. * @specified_logical_key: - * @callback: the callback to report the result. It should be called exactly - * once. Must not be null. - * @user_data: a value that will be sent back in the callback. Can be null. + * @cancellable: (allow-none): a #GCancellable or %NULL. + * @callback: (scope async): a #GAsyncReadyCallback to call when the view is + * added. + * @user_data: (closure): user data to pass to @callback. * - * Let the responder handle an event, expecting the responder to report - * whether to handle the event. The result will be reported by invoking - * `callback` exactly once, which might happen after - * `fl_key_embedder_responder_handle_event` or during it. + * Let the responder handle an event, expecting the responder to report whether + * to handle the event. */ -void fl_key_embedder_responder_handle_event( +void fl_key_embedder_responder_handle_event(FlKeyEmbedderResponder* responder, + FlKeyEvent* event, + uint64_t specified_logical_key, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data); + +/** + * fl_key_embedder_responder_handle_event_finish: + * @responder: an #FlKeyEmbedderResponder. + * @result: a #GAsyncResult. + * @handled: location to write if this event was handled by the embedder. + * @error: (allow-none): #GError location to store the error occurring, or %NULL + * to ignore. + * + * Completes request started with fl_key_embedder_responder_handle_event(). + * + * Returns %TRUE on success. + */ +gboolean fl_key_embedder_responder_handle_event_finish( FlKeyEmbedderResponder* responder, - FlKeyEvent* event, - uint64_t specified_logical_key, - FlKeyEmbedderResponderAsyncCallback callback, - gpointer user_data); + GAsyncResult* result, + gboolean* handled, + GError** error); /** * fl_key_embedder_responder_sync_modifiers_if_needed: diff --git a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc index bf766dfc33..5d8f8a404e 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc @@ -8,8 +8,7 @@ #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" -#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" -#include "flutter/shell/platform/linux/fl_engine_private.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_engine.h" #include "flutter/shell/platform/linux/testing/fl_test.h" namespace { @@ -97,49 +96,49 @@ static FlKeyEmbedderCallRecord* fl_key_embedder_call_record_new( return self; } -static gboolean g_expected_handled; -static gpointer g_expected_user_data; - -static void verify_response_handled(bool handled, gpointer user_data) { - EXPECT_EQ(handled, g_expected_handled); -} - -static void invoke_record_callback_and_verify(FlKeyEmbedderCallRecord* record, - bool expected_handled, - void* expected_user_data) { +static void invoke_record_callback(FlKeyEmbedderCallRecord* record, + bool expected_handled) { g_return_if_fail(record->callback != nullptr); - g_expected_handled = expected_handled; - g_expected_user_data = expected_user_data; record->callback(expected_handled, record->user_data); } -static void record_calls(const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* callback_user_data, - void* send_key_event_user_data) { - GPtrArray* records_array = - reinterpret_cast(send_key_event_user_data); - if (records_array != nullptr) { - g_ptr_array_add(records_array, fl_key_embedder_call_record_new( - event, callback, callback_user_data)); - } -} - // Basic key presses TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // On a QWERTY keyboard, press key Q (physically key A), and release. // Key down g_autoptr(FlKeyEvent) event1 = fl_key_event_new(12345, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -152,15 +151,26 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { EXPECT_STREQ(record->event->character, "a"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Key up g_autoptr(FlKeyEvent) event2 = fl_key_event_new(12346, kRelease, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, FALSE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -172,7 +182,8 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, FALSE, &user_data); + invoke_record_callback(record, FALSE); + g_main_loop_run(loop2); clear_records(call_records); // On an AZERTY keyboard, press key Q (physically key A), and release. @@ -180,8 +191,18 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { g_autoptr(FlKeyEvent) event3 = fl_key_event_new(12347, kPress, kKeyCodeKeyA, GDK_KEY_q, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -193,15 +214,26 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { EXPECT_STREQ(record->event->character, "q"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // Key up g_autoptr(FlKeyEvent) event4 = fl_key_event_new(12348, kRelease, kKeyCodeKeyA, GDK_KEY_q, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, FALSE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -213,24 +245,47 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, FALSE, &user_data); + invoke_record_callback(record, FALSE); + g_main_loop_run(loop4); } // Basic key presses, but uses the specified logical key if it is not 0. TEST(FlKeyEmbedderResponderTest, UsesSpecifiedLogicalKey) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // On an AZERTY keyboard, press physical key 1, and release. // Key down g_autoptr(FlKeyEvent) event = fl_key_event_new(12345, kPress, kKeyCodeDigit1, GDK_KEY_ampersand, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event, kLogicalDigit1, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event, kLogicalDigit1, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -243,23 +298,46 @@ TEST(FlKeyEmbedderResponderTest, UsesSpecifiedLogicalKey) { EXPECT_STREQ(record->event->character, "&"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop); } // Press Shift, key A, then release Shift, key A. TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Press shift right g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeShiftRight, GDK_KEY_Shift_R, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -270,14 +348,25 @@ TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Press key A g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_SHIFT_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -287,14 +376,25 @@ TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { EXPECT_STREQ(record->event->character, "A"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Release shift right g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 103, kRelease, kKeyCodeShiftRight, GDK_KEY_Shift_R, GDK_SHIFT_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -304,15 +404,26 @@ TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // Release key A g_autoptr(FlKeyEvent) event4 = fl_key_event_new(104, kRelease, kKeyCodeKeyA, GDK_KEY_A, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -322,7 +433,8 @@ TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); } // Press or release Numpad 1 between presses/releases of NumLock. @@ -334,18 +446,40 @@ TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { // test-worthy because the keyval for the numpad key will change before and // after the NumLock tap, which should not alter the resulting logical key. TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Press Numpad 1 (stage 0) g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeNumpad1, GDK_KEY_KP_End, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -356,15 +490,26 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); // TODO(chrome-bot): EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Press NumLock (stage 0 -> 1) g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeNumLock, GDK_KEY_Num_Lock, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -374,14 +519,25 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Release numpad 1 (stage 1) g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 104, kRelease, kKeyCodeNumpad1, GDK_KEY_KP_1, GDK_MOD2_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -391,14 +547,25 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // Release NumLock (stage 1 -> 2) g_autoptr(FlKeyEvent) event4 = fl_key_event_new( 103, kRelease, kKeyCodeNumLock, GDK_KEY_Num_Lock, GDK_MOD2_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -408,14 +575,25 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); clear_records(call_records); // Press Numpad 1 (stage 2) g_autoptr(FlKeyEvent) event5 = fl_key_event_new( 101, kPress, kKeyCodeNumpad1, GDK_KEY_KP_End, GDK_MOD2_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event5, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop5 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event5, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop5); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -425,14 +603,25 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); // TODO(chrome-bot): EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop5); clear_records(call_records); // Press NumLock (stage 2 -> 3) g_autoptr(FlKeyEvent) event6 = fl_key_event_new( 102, kPress, kKeyCodeNumLock, GDK_KEY_Num_Lock, GDK_MOD2_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event6, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop6 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event6, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop6); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -442,14 +631,25 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop6); clear_records(call_records); // Release numpad 1 (stage 3) g_autoptr(FlKeyEvent) event7 = fl_key_event_new( 104, kRelease, kKeyCodeNumpad1, GDK_KEY_KP_1, GDK_MOD2_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event7, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop7 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event7, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop7); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -459,14 +659,25 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop7); clear_records(call_records); // Release NumLock (stage 3 -> 0) g_autoptr(FlKeyEvent) event8 = fl_key_event_new( 103, kRelease, kKeyCodeNumLock, GDK_KEY_Num_Lock, GDK_MOD2_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event8, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop8 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event8, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop8); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -476,7 +687,8 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop8); } // Press or release digit 1 between presses/releases of Shift. @@ -484,19 +696,41 @@ TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { // GTK will change the virtual key during a key tap, and the embedder // should regularize it. TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); GdkModifierType state = static_cast(0); // Press shift left g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 101, kPress, kKeyCodeShiftLeft, GDK_KEY_Shift_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -507,7 +741,8 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); state = GDK_SHIFT_MASK; @@ -515,8 +750,18 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { // Press digit 1, which is '!' on a US keyboard g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeDigit1, GDK_KEY_exclam, state, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -526,14 +771,25 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { EXPECT_STREQ(record->event->character, "!"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Release shift g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 103, kRelease, kKeyCodeShiftLeft, GDK_KEY_Shift_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -543,7 +799,8 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); state = static_cast(0); @@ -551,8 +808,18 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { // Release digit 1, which is "1" because shift has been released. g_autoptr(FlKeyEvent) event4 = fl_key_event_new(104, kRelease, kKeyCodeDigit1, GDK_KEY_1, state, 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -562,7 +829,8 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); } // Press or release letter key between presses/releases of CapsLock. @@ -570,18 +838,40 @@ TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { // This tests interaction between lock keys and non-lock keys in cases that do // not have events missed. TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Press CapsLock (stage 0 -> 1) g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -592,14 +882,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Press key A (stage 1) g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -609,14 +910,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, "A"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Release CapsLock (stage 1 -> 2) g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 103, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -626,14 +938,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // Release key A (stage 2) g_autoptr(FlKeyEvent) event4 = fl_key_event_new(104, kRelease, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -643,14 +966,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); clear_records(call_records); // Press CapsLock (stage 2 -> 3) g_autoptr(FlKeyEvent) event5 = fl_key_event_new( 105, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event5, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop5 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event5, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop5); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -660,14 +994,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop5); clear_records(call_records); // Press key A (stage 3) g_autoptr(FlKeyEvent) event6 = fl_key_event_new(106, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event6, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop6 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event6, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop6); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -677,14 +1022,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, "A"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop6); clear_records(call_records); // Release CapsLock (stage 3 -> 0) g_autoptr(FlKeyEvent) event7 = fl_key_event_new( 107, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event7, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop7 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event7, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop7); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -694,15 +1050,26 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop7); clear_records(call_records); // Release key A (stage 0) g_autoptr(FlKeyEvent) event8 = fl_key_event_new(108, kRelease, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event8, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop8 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event8, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop8); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -712,7 +1079,8 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop8); } // Press or release letter key between presses/releases of CapsLock, on @@ -720,17 +1088,39 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { // // This happens when using a Chrome remote desktop on MacOS. TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Press key A (stage 0) g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 101, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -741,14 +1131,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, "a"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Press CapsLock (stage 0 -> 1) g_autoptr(FlKeyEvent) event2 = fl_key_event_new( 102, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -758,14 +1159,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Release CapsLock (stage 1 -> 2) g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 103, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -775,14 +1187,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // Release key A (stage 2) g_autoptr(FlKeyEvent) event4 = fl_key_event_new(104, kRelease, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -792,14 +1215,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); clear_records(call_records); // Press key A (stage 2) g_autoptr(FlKeyEvent) event5 = fl_key_event_new(105, kPress, kKeyCodeKeyA, GDK_KEY_A, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event5, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop5 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event5, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop5); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -809,15 +1243,26 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, "A"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop5); clear_records(call_records); // Press CapsLock (stage 2 -> 3) g_autoptr(FlKeyEvent) event6 = fl_key_event_new(106, kPress, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event6, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop6 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event6, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop6); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -827,14 +1272,25 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop6); clear_records(call_records); // Release CapsLock (stage 3 -> 0) g_autoptr(FlKeyEvent) event7 = fl_key_event_new( 107, kRelease, kKeyCodeCapsLock, GDK_KEY_Caps_Lock, GDK_LOCK_MASK, 0); - fl_key_embedder_responder_handle_event(responder, event7, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop7 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event7, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop7); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -844,15 +1300,26 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop7); clear_records(call_records); // Release key A (stage 0) g_autoptr(FlKeyEvent) event8 = fl_key_event_new(108, kRelease, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event8, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop8 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event8, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop8); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -862,35 +1329,68 @@ TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop8); } TEST(FlKeyEmbedderResponderTest, TurnDuplicateDownEventsToRepeats) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Press KeyA g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 101, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Another KeyA down events, which usually means a repeated event. - g_expected_handled = false; g_autoptr(FlKeyEvent) event2 = fl_key_event_new( 102, kPress, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); @@ -902,35 +1402,68 @@ TEST(FlKeyEmbedderResponderTest, TurnDuplicateDownEventsToRepeats) { EXPECT_EQ(record->event->synthesized, false); EXPECT_NE(record->callback, nullptr); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Release KeyA g_autoptr(FlKeyEvent) event3 = fl_key_event_new(103, kRelease, kKeyCodeKeyA, GDK_KEY_q, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); } TEST(FlKeyEmbedderResponderTest, IgnoreAbruptUpEvent) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Release KeyA before it was even pressed. - g_expected_handled = true; // The empty event is always handled. g_autoptr(FlKeyEvent) event = fl_key_event_new(103, kRelease, kKeyCodeKeyA, GDK_KEY_q, static_cast(0), 0); - fl_key_embedder_responder_handle_event(responder, event, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop); EXPECT_EQ(call_records->len, 1u); @@ -940,17 +1473,31 @@ TEST(FlKeyEmbedderResponderTest, IgnoreAbruptUpEvent) { EXPECT_EQ(record->event->logical, 0ull); EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - EXPECT_EQ(record->callback, nullptr); + + invoke_record_callback(record, TRUE); + g_main_loop_run(loop); } // Test if missed modifier keys can be detected and synthesized with state // information upon events that are for this modifier key. TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Test 1: synthesize key down. @@ -960,8 +1507,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { // Send a ControlLeft up g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 101, kRelease, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 2u); FlKeyEmbedderCallRecord* record = @@ -981,7 +1538,8 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // Test 2: synthesize key up. @@ -990,11 +1548,22 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { state = static_cast(0); g_autoptr(FlKeyEvent) event2 = fl_key_event_new( 102, kPress, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // A key up of control left is missed. @@ -1003,8 +1572,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { // Send another ControlLeft down g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 103, kPress, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 2u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -1023,18 +1602,30 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // Send a ControlLeft up to clear up state. state = GDK_CONTROL_MASK; g_autoptr(FlKeyEvent) event4 = fl_key_event_new( 104, kRelease, kKeyCodeControlLeft, GDK_KEY_Control_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); clear_records(call_records); // Test 3: synthesize by right modifier. @@ -1045,8 +1636,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { // Send a ControlRight up. g_autoptr(FlKeyEvent) event5 = fl_key_event_new( 105, kRelease, kKeyCodeControlRight, GDK_KEY_Control_R, state, 0); - fl_key_embedder_responder_handle_event(responder, event5, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop5 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event5, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop5); // A ControlLeft down is synthesized, with an empty event. // Reason: The ControlLeft down is synthesized to synchronize the state @@ -1060,17 +1661,32 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { EXPECT_EQ(record->event->logical, kLogicalControlLeft); EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, true); + + invoke_record_callback(record, TRUE); + g_main_loop_run(loop5); } // Test if missed modifier keys can be detected and synthesized with state // information upon events that are not for this modifier key. TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnNonSelfEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // A key down of control left is missed. GdkModifierType state = GDK_CONTROL_MASK; @@ -1078,8 +1694,18 @@ TEST(FlKeyEmbedderResponderTest, // Send a normal event (KeyA down) g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeKeyA, GDK_KEY_a, state, 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 2u); FlKeyEmbedderCallRecord* record = @@ -1099,7 +1725,8 @@ TEST(FlKeyEmbedderResponderTest, EXPECT_STREQ(record->event->character, "a"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // A key up of control left is missed. @@ -1108,8 +1735,18 @@ TEST(FlKeyEmbedderResponderTest, // Send a normal event (KeyA up) g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kRelease, kKeyCodeKeyA, GDK_KEY_A, state, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 2u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -1128,7 +1765,8 @@ TEST(FlKeyEmbedderResponderTest, EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // Test non-default key mapping. @@ -1138,8 +1776,18 @@ TEST(FlKeyEmbedderResponderTest, g_autoptr(FlKeyEvent) event3 = fl_key_event_new(101, kPress, kKeyCodeCapsLock, GDK_KEY_Control_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -1150,7 +1798,8 @@ TEST(FlKeyEmbedderResponderTest, EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); clear_records(call_records); // The key up of the control left press is missed. @@ -1159,8 +1808,18 @@ TEST(FlKeyEmbedderResponderTest, // Send a normal event (KeyA down). g_autoptr(FlKeyEvent) event4 = fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, state, 0); - fl_key_embedder_responder_handle_event(responder, event4, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop4 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event4, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop4); // The synthesized event should have physical CapsLock and logical // ControlLeft. @@ -1181,26 +1840,49 @@ TEST(FlKeyEmbedderResponderTest, EXPECT_STREQ(record->event->character, "A"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop4); } // Test if missed modifier keys can be detected and synthesized with state // information upon events that do not have the standard key mapping. TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnRemappedEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // Press a key with physical CapsLock and logical ControlLeft. GdkModifierType state = static_cast(0); g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeCapsLock, GDK_KEY_Control_L, state, 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 1u); FlKeyEmbedderCallRecord* record = @@ -1212,7 +1894,8 @@ TEST(FlKeyEmbedderResponderTest, EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // The key up of the control left press is missed. @@ -1221,8 +1904,18 @@ TEST(FlKeyEmbedderResponderTest, // Send a normal event (KeyA down). g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeKeyA, GDK_KEY_A, state, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); // The synthesized event should have physical CapsLock and logical // ControlLeft. @@ -1243,17 +1936,30 @@ TEST(FlKeyEmbedderResponderTest, EXPECT_STREQ(record->event->character, "A"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); } // Test if missed lock keys can be detected and synthesized with state // information upon events that are not for this modifier key. TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // The NumLock is desynchronized by being enabled. GdkModifierType state = GDK_MOD2_MASK; @@ -1261,8 +1967,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { // Send a normal event g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeKeyA, GDK_KEY_a, state, 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 2u); FlKeyEmbedderCallRecord* record = @@ -1282,7 +1998,8 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { EXPECT_STREQ(record->event->character, "a"); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // The NumLock is desynchronized by being disabled. @@ -1291,8 +2008,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { // Release key A g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kRelease, kKeyCodeKeyA, GDK_KEY_A, state, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 4u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -1327,16 +2054,26 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); clear_records(call_records); // 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; g_autoptr(FlKeyEvent) event3 = fl_key_event_new( 103, kRelease, kKeyCodeNumLock, GDK_KEY_Num_Lock, state, 0); - fl_key_embedder_responder_handle_event(responder, event3, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop3 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event3, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop3); EXPECT_EQ(call_records->len, 1u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -1344,17 +2081,31 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { EXPECT_EQ(record->event->logical, 0ull); EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - EXPECT_EQ(record->callback, nullptr); + + invoke_record_callback(record, TRUE); + g_main_loop_run(loop3); } // Test if missed lock keys can be detected and synthesized with state // information upon events that are for this modifier key. TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnSelfEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // The NumLock is desynchronized by being enabled. GdkModifierType state = GDK_MOD2_MASK; @@ -1362,8 +2113,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnSelfEvents) { // NumLock down g_autoptr(FlKeyEvent) event1 = fl_key_event_new(101, kPress, kKeyCodeNumLock, GDK_KEY_Num_Lock, state, 0); - fl_key_embedder_responder_handle_event(responder, event1, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop1 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event1, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop1); EXPECT_EQ(call_records->len, 3u); FlKeyEmbedderCallRecord* record = @@ -1391,7 +2152,8 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnSelfEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop1); clear_records(call_records); // The NumLock is desynchronized by being enabled in a press event. @@ -1400,8 +2162,18 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnSelfEvents) { // NumLock up g_autoptr(FlKeyEvent) event2 = fl_key_event_new(102, kPress, kKeyCodeNumLock, GDK_KEY_Num_Lock, state, 0); - fl_key_embedder_responder_handle_event(responder, event2, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop2 = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event2, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop2); EXPECT_EQ(call_records->len, 4u); record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(call_records, 0)); @@ -1436,28 +2208,51 @@ TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnSelfEvents) { EXPECT_STREQ(record->event->character, nullptr); EXPECT_EQ(record->event->synthesized, false); - invoke_record_callback_and_verify(record, TRUE, &user_data); + invoke_record_callback(record, TRUE); + g_main_loop_run(loop2); } // Ensures that even if the primary event is ignored (due to duplicate // key up or down events), key synthesization is still performed. TEST(FlKeyEmbedderResponderTest, SynthesizationOccursOnIgnoredEvents) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); - int user_data = 123; // Arbitrary user data + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + return kSuccess; + })); // The NumLock is desynchronized by being enabled, and Control is pressed. GdkModifierType state = static_cast(GDK_MOD2_MASK | GDK_CONTROL_MASK); // Send a KeyA up event, which will be ignored. - g_expected_handled = true; // The ignored event is always handled. g_autoptr(FlKeyEvent) event = fl_key_event_new(101, kRelease, kKeyCodeKeyA, GDK_KEY_a, state, 0); - fl_key_embedder_responder_handle_event(responder, event, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop); + g_main_loop_run(loop); EXPECT_EQ(call_records->len, 2u); FlKeyEmbedderCallRecord* record = @@ -1489,23 +2284,46 @@ TEST(FlKeyEmbedderResponderTest, SynthesizationOccursOnIgnoredEvents) { // AltLeft down because the physical AltRight key corresponds to logical // MetaRight at the moment. TEST(FlKeyEmbedderResponderTest, HandlesShiftAltVersusGroupNext) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + callback(true, user_data); + return kSuccess; + })); - g_expected_handled = true; guint32 now_time = 1; // A convenient shorthand to simulate events. auto send_key_event = [responder, &now_time](bool is_press, guint keyval, guint16 keycode, GdkModifierType state) { now_time += 1; - int user_data = 123; // Arbitrary user data g_autoptr(FlKeyEvent) event = fl_key_event_new(now_time, is_press, keycode, keyval, state, 0); - fl_key_embedder_responder_handle_event(responder, event, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop); + g_main_loop_run(loop); }; send_key_event(kPress, GDK_KEY_Shift_L, kKeyCodeShiftLeft, @@ -1593,23 +2411,46 @@ TEST(FlKeyEmbedderResponderTest, HandlesShiftAltVersusGroupNext) { // key won't be the MetaLeft one. // Regression test for https://github.com/flutter/flutter/issues/96082 TEST(FlKeyEmbedderResponderTest, HandlesShiftAltLeftIsMetaLeft) { + g_autoptr(FlDartProject) project = fl_dart_project_new(); + g_autoptr(FlEngine) engine = fl_engine_new(project); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + + g_autoptr(FlKeyEmbedderResponder) responder = + fl_key_embedder_responder_new(engine); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func(g_object_unref); - g_autoptr(FlKeyEmbedderResponder) responder = - fl_key_embedder_responder_new(record_calls, call_records); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(call_records, fl_key_embedder_call_record_new( + event, callback, user_data)); + callback(true, user_data); + return kSuccess; + })); - g_expected_handled = true; guint32 now_time = 1; // A convenient shorthand to simulate events. auto send_key_event = [responder, &now_time](bool is_press, guint keyval, guint16 keycode, GdkModifierType state) { now_time += 1; - int user_data = 123; // Arbitrary user data g_autoptr(FlKeyEvent) event = fl_key_event_new(now_time, is_press, keycode, keyval, state, 0); - fl_key_embedder_responder_handle_event(responder, event, 0, - verify_response_handled, &user_data); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + fl_key_embedder_responder_handle_event( + responder, event, 0, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + gboolean handled; + EXPECT_TRUE(fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, nullptr)); + EXPECT_EQ(handled, TRUE); + + g_main_loop_quit(static_cast(user_data)); + }, + loop); + g_main_loop_run(loop); }; // ShiftLeft + AltLeft diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_handler.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_handler.cc index 9a1478da7f..baa19c6bd4 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_handler.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_handler.cc @@ -5,6 +5,7 @@ #include "flutter/shell/platform/linux/fl_keyboard_handler.h" #include "flutter/shell/platform/linux/fl_keyboard_channel.h" +#include "flutter/shell/platform/linux/key_mapping.h" struct _FlKeyboardHandler { GObject parent_instance; @@ -29,8 +30,8 @@ static FlValue* get_keyboard_state(gpointer user_data) { g_hash_table_foreach( pressing_records, [](gpointer key, gpointer value, gpointer user_data) { - int64_t physical_key = reinterpret_cast(key); - int64_t logical_key = reinterpret_cast(value); + int64_t physical_key = gpointer_to_uint64(key); + int64_t logical_key = gpointer_to_uint64(value); FlValue* fl_value_map = reinterpret_cast(user_data); fl_value_set_take(fl_value_map, fl_value_new_int(physical_key), @@ -66,9 +67,8 @@ FlKeyboardHandler* fl_keyboard_handler_new( g_object_new(fl_keyboard_handler_get_type(), nullptr)); self->keyboard_manager = FL_KEYBOARD_MANAGER(g_object_ref(keyboard_manager)); - - // Setup the flutter/keyboard channel. self->channel = fl_keyboard_channel_new(messenger, &keyboard_channel_vtable, self); + return self; } diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_handler_test.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_handler_test.cc index b3e4f6aee4..756a1e348f 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_handler_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_handler_test.cc @@ -4,40 +4,69 @@ #include "flutter/shell/platform/linux/fl_keyboard_handler.h" -#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" +#include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/fl_method_codec_private.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" #include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" +#include "flutter/shell/platform/linux/testing/mock_keymap.h" #include "gmock/gmock.h" #include "gtest/gtest.h" static constexpr char kKeyboardChannelName[] = "flutter/keyboard"; static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; -static constexpr uint64_t kMockPhysicalKey = 42; -static constexpr uint64_t kMockLogicalKey = 42; + +using ::flutter::testing::keycodes::kLogicalKeyA; +using ::flutter::testing::keycodes::kPhysicalKeyA; + +constexpr guint16 kKeyCodeKeyA = 0x26u; TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) { + ::testing::NiceMock mock_keymap; + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_get_pressed_state_handler( - manager, - [](gpointer user_data) { - GHashTable* result = g_hash_table_new(g_direct_hash, g_direct_equal); - g_hash_table_insert(result, - reinterpret_cast(kMockPhysicalKey), - reinterpret_cast(kMockLogicalKey)); - return result; - }, - nullptr); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + g_autoptr(FlKeyboardHandler) handler = fl_keyboard_handler_new(FL_BINARY_MESSENGER(messenger), manager); EXPECT_NE(handler, nullptr); + // Send key event to set pressed state. + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/keyevent", + [](FlMockBinaryMessenger* messenger, GTask* task, FlValue* message, + gpointer user_data) { + FlValue* response = fl_value_new_map(); + fl_value_set_string_take(response, "handled", fl_value_new_bool(FALSE)); + return response; + }, + nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(false, user_data); + return kSuccess; + })); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + g_autoptr(FlKeyEvent) event = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + fl_keyboard_manager_handle_event( + manager, event, nullptr, + [](GObject* object, GAsyncResult* result, gpointer user_data) { + g_autoptr(FlKeyEvent) redispatched_event = nullptr; + EXPECT_TRUE(fl_keyboard_manager_handle_event_finish( + FL_KEYBOARD_MANAGER(object), result, &redispatched_event, nullptr)); + g_main_loop_quit(static_cast(user_data)); + }, + loop); + g_main_loop_run(loop); + gboolean called = FALSE; fl_mock_binary_messenger_invoke_standard_method( messenger, kKeyboardChannelName, kGetKeyboardStateMethod, nullptr, @@ -49,14 +78,13 @@ TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) { EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response)); g_autoptr(FlValue) expected_result = fl_value_new_map(); - fl_value_set_take(expected_result, fl_value_new_int(kMockPhysicalKey), - fl_value_new_int(kMockLogicalKey)); + fl_value_set_take(expected_result, fl_value_new_int(kPhysicalKeyA), + fl_value_new_int(kLogicalKeyA)); EXPECT_TRUE(fl_value_equal(fl_method_success_response_get_result( FL_METHOD_SUCCESS_RESPONSE(response)), expected_result)); }, &called); - EXPECT_TRUE(called); - fl_binary_messenger_shutdown(FL_BINARY_MESSENGER(messenger)); + EXPECT_TRUE(called); } diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc index 9406eda257..7a81da289e 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc @@ -9,7 +9,6 @@ #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" @@ -84,17 +83,9 @@ static void handle_event_data_free(HandleEventData* data) { struct _FlKeyboardManager { GObject parent_instance; - GWeakRef engine; - - FlKeyboardManagerSendKeyEventHandler send_key_event_handler; - gpointer send_key_event_handler_user_data; - FlKeyboardManagerLookupKeyHandler lookup_key_handler; gpointer lookup_key_handler_user_data; - FlKeyboardManagerGetPressedStateHandler get_pressed_state_handler; - gpointer get_pressed_state_handler_user_data; - // Key events that have been redispatched. GPtrArray* redispatched_key_events; @@ -166,14 +157,25 @@ static void complete_handle_event(FlKeyboardManager* self, GTask* task) { g_task_return_boolean(task, TRUE); } -static void responder_handle_embedder_event_callback(bool handled, - gpointer user_data) { +static void responder_handle_embedder_event_cb(GObject* object, + GAsyncResult* result, + gpointer user_data) { g_autoptr(GTask) task = G_TASK(user_data); FlKeyboardManager* self = FL_KEYBOARD_MANAGER(g_task_get_source_object(task)); HandleEventData* data = static_cast(g_task_get_task_data(G_TASK(task))); data->embedder_responded = TRUE; + + g_autoptr(GError) error = nullptr; + gboolean handled; + if (!fl_key_embedder_responder_handle_event_finish( + FL_KEY_EMBEDDER_RESPONDER(object), result, &handled, &error)) { + if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { + g_warning("Failed to handle key event in embedder: %s", error->message); + } + handled = FALSE; + } if (handled) { data->handled = TRUE; } @@ -309,8 +311,6 @@ static void fl_keyboard_manager_dispose(GObject* object) { g_cancellable_cancel(self->cancellable); - g_weak_ref_clear(&self->engine); - self->keycode_to_goals.reset(); self->logical_to_mandatory_goals.reset(); @@ -357,51 +357,7 @@ FlKeyboardManager* fl_keyboard_manager_new(FlEngine* engine) { FlKeyboardManager* self = FL_KEYBOARD_MANAGER( g_object_new(fl_keyboard_manager_get_type(), nullptr)); - g_weak_ref_init(&self->engine, engine); - - self->key_embedder_responder = fl_key_embedder_responder_new( - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, void* send_key_event_user_data) { - FlKeyboardManager* self = FL_KEYBOARD_MANAGER(send_key_event_user_data); - if (self->send_key_event_handler != nullptr) { - self->send_key_event_handler(event, callback, callback_user_data, - self->send_key_event_handler_user_data); - } else { - g_autoptr(FlEngine) engine = FL_ENGINE(g_weak_ref_get(&self->engine)); - if (engine != nullptr) { - 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); - } - } - }, - self); + self->key_embedder_responder = fl_key_embedder_responder_new(engine); self->key_channel_responder = fl_key_channel_responder_new(fl_engine_get_binary_messenger(engine)); @@ -444,7 +400,8 @@ void fl_keyboard_manager_handle_event(FlKeyboardManager* self, fl_key_event_get_keycode(event)); fl_key_embedder_responder_handle_event( self->key_embedder_responder, event, specified_logical_key, - responder_handle_embedder_event_callback, g_object_ref(task)); + self->cancellable, responder_handle_embedder_event_cb, + g_object_ref(task)); fl_key_channel_responder_handle_event( self->key_channel_responder, event, specified_logical_key, self->cancellable, responder_handle_channel_event_cb, g_object_ref(task)); @@ -477,22 +434,8 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self, GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) { g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr); - if (self->get_pressed_state_handler != nullptr) { - return self->get_pressed_state_handler( - self->get_pressed_state_handler_user_data); - } else { - return fl_key_embedder_responder_get_pressed_state( - self->key_embedder_responder); - } -} - -void fl_keyboard_manager_set_send_key_event_handler( - FlKeyboardManager* self, - FlKeyboardManagerSendKeyEventHandler send_key_event_handler, - gpointer user_data) { - g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self)); - self->send_key_event_handler = send_key_event_handler; - self->send_key_event_handler_user_data = user_data; + return fl_key_embedder_responder_get_pressed_state( + self->key_embedder_responder); } void fl_keyboard_manager_set_lookup_key_handler( @@ -503,12 +446,3 @@ void fl_keyboard_manager_set_lookup_key_handler( self->lookup_key_handler = lookup_key_handler; self->lookup_key_handler_user_data = user_data; } - -void fl_keyboard_manager_set_get_pressed_state_handler( - FlKeyboardManager* self, - FlKeyboardManagerGetPressedStateHandler get_pressed_state_handler, - gpointer user_data) { - g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self)); - self->get_pressed_state_handler = get_pressed_state_handler; - self->get_pressed_state_handler_user_data = user_data; -} diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h index 3321b7044a..dea4153f5f 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h @@ -115,23 +115,6 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager, */ GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager); -typedef void (*FlKeyboardManagerSendKeyEventHandler)( - const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* callback_user_data, - gpointer user_data); - -/** - * fl_keyboard_manager_set_send_key_event_handler: - * @manager: the #FlKeyboardManager self. - * - * Set the handler for sending events, for testing purposes only. - */ -void fl_keyboard_manager_set_send_key_event_handler( - FlKeyboardManager* manager, - FlKeyboardManagerSendKeyEventHandler send_key_event_handler, - gpointer user_data); - typedef guint (*FlKeyboardManagerLookupKeyHandler)(const GdkKeymapKey* key, gpointer user_data); @@ -146,20 +129,6 @@ void fl_keyboard_manager_set_lookup_key_handler( FlKeyboardManagerLookupKeyHandler lookup_key_handler, gpointer user_data); -typedef GHashTable* (*FlKeyboardManagerGetPressedStateHandler)( - gpointer user_data); - -/** - * fl_keyboard_manager_set_get_pressed_state_handler: - * @manager: the #FlKeyboardManager self. - * - * Set the handler for gettting the keyboard state, for testing purposes only. - */ -void fl_keyboard_manager_set_get_pressed_state_handler( - FlKeyboardManager* manager, - FlKeyboardManagerGetPressedStateHandler get_pressed_state_handler, - gpointer user_data); - G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_ diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc index f5ced8f3b2..ed2f9cbd2d 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc @@ -8,6 +8,7 @@ #include #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" +#include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" #include "flutter/shell/platform/linux/fl_engine_private.h" #include "flutter/shell/platform/linux/key_mapping.h" #include "flutter/shell/platform/linux/testing/fl_mock_binary_messenger.h" @@ -15,16 +16,6 @@ #include "gtest/gtest.h" -// Define compound `expect` in macros. If they were defined in functions, the -// stacktrace wouldn't print where the function is called in the unit tests. - -#define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \ - EXPECT_EQ((RECORD)->event_type, (TYPE)); \ - EXPECT_EQ((RECORD)->event_physical, (PHYSICAL)); \ - EXPECT_EQ((RECORD)->event_logical, (LOGICAL)); \ - EXPECT_STREQ((RECORD)->event_character, (CHAR)); \ - EXPECT_EQ((RECORD)->event_synthesized, (SYNTHESIZED)); - #define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR) \ EXPECT_EQ(static_cast(g_ptr_array_index(call_records, 0)) \ ->event_type, \ @@ -134,16 +125,16 @@ TEST(FlKeyboardManagerTest, EngineNoResponseChannelHandled) { nullptr); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + // Don't handle first event - async call never completes. - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) {}, - nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, + void* user_data) { return kSuccess; })); g_autoptr(FlKeyEvent) event1 = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); gboolean first_event_completed = FALSE; @@ -156,12 +147,12 @@ TEST(FlKeyboardManagerTest, EngineNoResponseChannelHandled) { &first_event_completed); // Handle second event. - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, - gpointer user_data) { callback(true, callback_user_data); }, - nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(true, user_data); + return kSuccess; + })); g_autoptr(FlKeyEvent) event2 = fl_key_event_new( 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); @@ -188,12 +179,10 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelNotHandledSync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls synchronously. fl_mock_binary_messenger_set_json_message_channel( @@ -206,12 +195,12 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelNotHandledSync) { return fl_value_ref(return_value); }, nullptr); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, - gpointer user_data) { callback(true, callback_user_data); }, - nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(true, user_data); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -235,12 +224,10 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelHandledSync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls synchronously. fl_mock_binary_messenger_set_json_message_channel( @@ -253,12 +240,12 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelHandledSync) { return fl_value_ref(return_value); }, nullptr); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, - gpointer user_data) { callback(false, callback_user_data); }, - nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(false, user_data); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -282,12 +269,10 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelHandledSync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls synchronously. fl_mock_binary_messenger_set_json_message_channel( @@ -300,12 +285,12 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelHandledSync) { return fl_value_ref(return_value); }, nullptr); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, - gpointer user_data) { callback(true, callback_user_data); }, - nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(true, user_data); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -329,12 +314,10 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelNotHandledSync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls synchronously. fl_mock_binary_messenger_set_json_message_channel( @@ -347,12 +330,12 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelNotHandledSync) { return fl_value_ref(return_value); }, nullptr); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, - gpointer user_data) { callback(false, callback_user_data); }, - nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(false, user_data); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -384,12 +367,10 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelNotHandledAsync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls asynchronously. g_autoptr(GPtrArray) channel_calls = @@ -406,15 +387,14 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelNotHandledAsync) { channel_calls); g_autoptr(GPtrArray) embedder_call_records = g_ptr_array_new_with_free_func( reinterpret_cast(call_record_free)); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) { - GPtrArray* call_records = static_cast(user_data); - g_ptr_array_add(call_records, - call_record_new(event, callback, callback_user_data)); - }, - embedder_call_records); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([&embedder_call_records]( + auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(embedder_call_records, + call_record_new(event, callback, user_data)); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -448,12 +428,10 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelHandledAsync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls asynchronously. g_autoptr(GPtrArray) channel_calls = @@ -470,15 +448,14 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelHandledAsync) { channel_calls); g_autoptr(GPtrArray) embedder_call_records = g_ptr_array_new_with_free_func( reinterpret_cast(call_record_free)); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) { - GPtrArray* call_records = static_cast(user_data); - g_ptr_array_add(call_records, - call_record_new(event, callback, callback_user_data)); - }, - embedder_call_records); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([&embedder_call_records]( + auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(embedder_call_records, + call_record_new(event, callback, user_data)); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -512,12 +489,10 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelHandledAsync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls asynchronously. g_autoptr(GPtrArray) channel_calls = @@ -534,15 +509,14 @@ TEST(FlKeyboardManagerTest, EngineHandledChannelHandledAsync) { channel_calls); g_autoptr(GPtrArray) embedder_call_records = g_ptr_array_new_with_free_func( reinterpret_cast(call_record_free)); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) { - GPtrArray* call_records = static_cast(user_data); - g_ptr_array_add(call_records, - call_record_new(event, callback, callback_user_data)); - }, - embedder_call_records); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([&embedder_call_records]( + auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(embedder_call_records, + call_record_new(event, callback, user_data)); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -576,12 +550,10 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelNotHandledAsync) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + + EXPECT_TRUE(fl_engine_start(engine, nullptr)); // Handle channel and embedder calls asynchronously. g_autoptr(GPtrArray) channel_calls = @@ -598,15 +570,14 @@ TEST(FlKeyboardManagerTest, EngineNotHandledChannelNotHandledAsync) { channel_calls); g_autoptr(GPtrArray) embedder_call_records = g_ptr_array_new_with_free_func( reinterpret_cast(call_record_free)); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) { - GPtrArray* call_records = static_cast(user_data); - g_ptr_array_add(call_records, - call_record_new(event, callback, callback_user_data)); - }, - embedder_call_records); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([&embedder_call_records]( + auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + g_ptr_array_add(embedder_call_records, + call_record_new(event, callback, user_data)); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); @@ -641,20 +612,18 @@ TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) { g_autoptr(FlEngine) engine = fl_engine_new(project); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func( reinterpret_cast(call_record_free)); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) { - GPtrArray* call_records = static_cast(user_data); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { g_ptr_array_add(call_records, - call_record_new(event, callback, callback_user_data)); - }, - call_records); - fl_keyboard_manager_set_lookup_key_handler( - manager, [](const GdkKeymapKey* key, gpointer user_data) { return 0u; }, - nullptr); + call_record_new(event, callback, user_data)); + return kSuccess; + })); auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) { g_autoptr(FlKeyEvent) event1 = fl_key_event_new( @@ -776,17 +745,18 @@ TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) { g_autoptr(FlEngine) engine = fl_engine_new(project); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + g_autoptr(GPtrArray) call_records = g_ptr_array_new_with_free_func( reinterpret_cast(call_record_free)); - fl_keyboard_manager_set_send_key_event_handler( - manager, - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, gpointer user_data) { - GPtrArray* call_records = static_cast(user_data); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, + ([&call_records](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { g_ptr_array_add(call_records, - call_record_new(event, callback, callback_user_data)); - }, - call_records); + call_record_new(event, callback, user_data)); + return kSuccess; + })); auto verifyModifierIsSynthesized = [&](GdkModifierType mask, uint64_t physical, uint64_t logical) { @@ -794,16 +764,23 @@ TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) { guint state = mask; fl_keyboard_manager_sync_modifier_if_needed(manager, state, 1000); EXPECT_EQ(call_records->len, 1u); - EXPECT_KEY_EVENT( - static_cast(g_ptr_array_index(call_records, 0)), - kFlutterKeyEventTypeDown, physical, logical, NULL, true); + CallRecord* record = + static_cast(g_ptr_array_index(call_records, 0)); + EXPECT_EQ(record->event_type, kFlutterKeyEventTypeDown); + EXPECT_EQ(record->event_physical, physical); + EXPECT_EQ(record->event_logical, logical); + EXPECT_STREQ(record->event_character, NULL); + EXPECT_EQ(record->event_synthesized, true); // Modifier is released. state = state ^ mask; fl_keyboard_manager_sync_modifier_if_needed(manager, state, 1001); EXPECT_EQ(call_records->len, 2u); - EXPECT_KEY_EVENT( - static_cast(g_ptr_array_index(call_records, 1)), - kFlutterKeyEventTypeUp, physical, logical, NULL, true); + record = static_cast(g_ptr_array_index(call_records, 1)); + EXPECT_EQ(record->event_type, kFlutterKeyEventTypeUp); + EXPECT_EQ(record->event_physical, physical); + EXPECT_EQ(record->event_logical, logical); + EXPECT_STREQ(record->event_character, NULL); + EXPECT_EQ(record->event_synthesized, true); g_ptr_array_set_size(call_records, 0); }; @@ -826,27 +803,49 @@ TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) { TEST(FlKeyboardManagerTest, GetPressedState) { ::testing::NiceMock mock_keymap; - g_autoptr(FlDartProject) project = fl_dart_project_new(); - g_autoptr(FlEngine) engine = fl_engine_new(project); + g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); + g_autoptr(FlEngine) engine = + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(engine); + EXPECT_TRUE(fl_engine_start(engine, nullptr)); + // Dispatch a key event. + fl_mock_binary_messenger_set_json_message_channel( + messenger, "flutter/keyevent", + [](FlMockBinaryMessenger* messenger, GTask* task, FlValue* message, + gpointer user_data) { + FlValue* response = fl_value_new_map(); + fl_value_set_string_take(response, "handled", fl_value_new_bool(FALSE)); + return response; + }, + nullptr); + fl_engine_get_embedder_api(engine)->SendKeyEvent = MOCK_ENGINE_PROC( + SendKeyEvent, ([](auto engine, const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, void* user_data) { + callback(false, user_data); + return kSuccess; + })); g_autoptr(FlKeyEvent) event = fl_key_event_new( 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); fl_keyboard_manager_handle_event( manager, event, nullptr, [](GObject* object, GAsyncResult* result, gpointer user_data) { g_autoptr(FlKeyEvent) redispatched_event = nullptr; EXPECT_TRUE(fl_keyboard_manager_handle_event_finish( FL_KEYBOARD_MANAGER(object), result, &redispatched_event, nullptr)); + EXPECT_NE(redispatched_event, nullptr); + g_main_loop_quit(static_cast(user_data)); }, - nullptr); + loop); + g_main_loop_run(loop); - GHashTable* pressedState = fl_keyboard_manager_get_pressed_state(manager); - EXPECT_EQ(g_hash_table_size(pressedState), 1u); + GHashTable* pressed_state = fl_keyboard_manager_get_pressed_state(manager); + EXPECT_EQ(g_hash_table_size(pressed_state), 1u); gpointer physical_key = - g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA)); + g_hash_table_lookup(pressed_state, uint64_to_gpointer(kPhysicalKeyA)); EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA); } diff --git a/engine/src/flutter/shell/platform/linux/fl_settings_handler_test.cc b/engine/src/flutter/shell/platform/linux/fl_settings_handler_test.cc index c46d39aaaf..29e8a890ea 100644 --- a/engine/src/flutter/shell/platform/linux/fl_settings_handler_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_settings_handler_test.cc @@ -19,8 +19,7 @@ TEST(FlSettingsHandlerTest, AlwaysUse24HourFormat) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlSettingsHandler) handler = fl_settings_handler_new(engine); EXPECT_CALL(settings, fl_settings_get_clock_format( @@ -78,8 +77,7 @@ TEST(FlSettingsHandlerTest, PlatformBrightness) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlSettingsHandler) handler = fl_settings_handler_new(engine); EXPECT_CALL(settings, fl_settings_get_color_scheme( @@ -135,8 +133,7 @@ TEST(FlSettingsHandlerTest, TextScaleFactor) { g_autoptr(FlMockBinaryMessenger) messenger = fl_mock_binary_messenger_new(); g_autoptr(FlEngine) engine = - FL_ENGINE(g_object_new(fl_engine_get_type(), "binary-messenger", - FL_BINARY_MESSENGER(messenger), nullptr)); + fl_engine_new_with_binary_messenger(FL_BINARY_MESSENGER(messenger)); g_autoptr(FlSettingsHandler) handler = fl_settings_handler_new(engine); EXPECT_CALL(settings, fl_settings_get_text_scaling_factor(