diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index fd9a96d65e74d..283a93a5b1fdf 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -44799,6 +44799,9 @@ ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc + ../. ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_layout_test.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc + ../../../flutter/LICENSE @@ -47699,6 +47702,9 @@ FILE: ../../../flutter/shell/platform/linux/fl_keyboard_handler_test.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout.h FILE: ../../../flutter/shell/platform/linux/fl_keyboard_layout_test.cc +FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.cc +FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager.h +FILE: ../../../flutter/shell/platform/linux/fl_keyboard_manager_test.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.cc FILE: ../../../flutter/shell/platform/linux/fl_keyboard_pending_event.h FILE: ../../../flutter/shell/platform/linux/fl_keyboard_view_delegate.cc diff --git a/shell/platform/linux/BUILD.gn b/shell/platform/linux/BUILD.gn index 22c192e12a011..a28e375154cdb 100644 --- a/shell/platform/linux/BUILD.gn +++ b/shell/platform/linux/BUILD.gn @@ -81,6 +81,7 @@ source_set("flutter_linux_sources") { "fl_dart_project_private.h", "fl_engine_private.h", "fl_keyboard_handler.h", + "fl_keyboard_manager.h", "fl_keyboard_pending_event.h", "fl_keyboard_view_delegate.h", "fl_key_event.h", @@ -118,6 +119,7 @@ source_set("flutter_linux_sources") { "fl_key_responder.cc", "fl_keyboard_handler.cc", "fl_keyboard_layout.cc", + "fl_keyboard_manager.cc", "fl_keyboard_pending_event.cc", "fl_keyboard_view_delegate.cc", "fl_message_codec.cc", @@ -217,6 +219,7 @@ executable("flutter_linux_unittests") { "fl_key_embedder_responder_test.cc", "fl_keyboard_handler_test.cc", "fl_keyboard_layout_test.cc", + "fl_keyboard_manager_test.cc", "fl_message_codec_test.cc", "fl_method_channel_test.cc", "fl_method_codec_test.cc", diff --git a/shell/platform/linux/fl_keyboard_handler.cc b/shell/platform/linux/fl_keyboard_handler.cc index e91492519cd39..266b9b1e007ca 100644 --- a/shell/platform/linux/fl_keyboard_handler.cc +++ b/shell/platform/linux/fl_keyboard_handler.cc @@ -4,376 +4,23 @@ #include "flutter/shell/platform/linux/fl_keyboard_handler.h" -#include -#include -#include -#include - -#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" -#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h" -#include "flutter/shell/platform/linux/key_mapping.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" -// Turn on this flag to print complete layout data when switching IMEs. The data -// is used in unit tests. -#define DEBUG_PRINT_LAYOUT - static constexpr char kChannelName[] = "flutter/keyboard"; static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; -/* Declarations of private classes */ - -G_DECLARE_FINAL_TYPE(FlKeyboardHandlerUserData, - fl_keyboard_handler_user_data, - FL, - KEYBOARD_HANDLER_USER_DATA, - GObject); - -/* End declarations */ - -namespace { - -static bool is_eascii(uint16_t character) { - return character < 256; -} - -#ifdef DEBUG_PRINT_LAYOUT -// Prints layout entries that will be parsed by `MockLayoutData`. -void debug_format_layout_data(std::string& debug_layout_data, - uint16_t keycode, - uint16_t clue1, - uint16_t clue2) { - if (keycode % 4 == 0) { - debug_layout_data.append(" "); - } - - constexpr int kBufferSize = 30; - char buffer[kBufferSize]; - buffer[0] = 0; - buffer[kBufferSize - 1] = 0; - - snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2); - debug_layout_data.append(buffer); - - if (keycode % 4 == 3) { - snprintf(buffer, kBufferSize, " // 0x%02x", keycode); - debug_layout_data.append(buffer); - } -} -#endif - -} // namespace - -/* Define FlKeyboardHandlerUserData */ - -/** - * FlKeyboardHandlerUserData: - * The user_data used when #FlKeyboardHandler sends event to - * responders. - */ - -struct _FlKeyboardHandlerUserData { - GObject parent_instance; - - // The owner handler. - GWeakRef handler; - uint64_t sequence_id; -}; - -G_DEFINE_TYPE(FlKeyboardHandlerUserData, - fl_keyboard_handler_user_data, - G_TYPE_OBJECT) - -static void fl_keyboard_handler_user_data_dispose(GObject* object) { - g_return_if_fail(FL_IS_KEYBOARD_HANDLER_USER_DATA(object)); - FlKeyboardHandlerUserData* self = FL_KEYBOARD_HANDLER_USER_DATA(object); - - g_weak_ref_clear(&self->handler); - - G_OBJECT_CLASS(fl_keyboard_handler_user_data_parent_class)->dispose(object); -} - -static void fl_keyboard_handler_user_data_class_init( - FlKeyboardHandlerUserDataClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_user_data_dispose; -} - -static void fl_keyboard_handler_user_data_init( - FlKeyboardHandlerUserData* self) {} - -// Creates a new FlKeyboardHandlerUserData private class with all information. -static FlKeyboardHandlerUserData* fl_keyboard_handler_user_data_new( - FlKeyboardHandler* handler, - uint64_t sequence_id) { - FlKeyboardHandlerUserData* self = FL_KEYBOARD_HANDLER_USER_DATA( - g_object_new(fl_keyboard_handler_user_data_get_type(), nullptr)); - - g_weak_ref_init(&self->handler, handler); - self->sequence_id = sequence_id; - return self; -} - -/* Define FlKeyboardHandler */ - struct _FlKeyboardHandler { GObject parent_instance; GWeakRef view_delegate; - // An array of #FlKeyResponder. Elements are added with - // #fl_keyboard_handler_add_responder immediately after initialization and are - // automatically released on dispose. - GPtrArray* responder_list; - - // An array of #FlKeyboardPendingEvent. - // - // Its elements are *not* unreferenced when removed. When FlKeyboardHandler is - // disposed, this array will be set with a free_func so that the elements are - // unreferenced when removed. - GPtrArray* pending_responds; - - // An array of #FlKeyboardPendingEvent. - // - // Its elements are unreferenced when removed. - GPtrArray* pending_redispatches; - - // The last sequence ID used. Increased by 1 by every use. - uint64_t last_sequence_id; - - // Record the derived layout. - // - // It is cleared when the platform reports a layout switch. Each entry, - // which corresponds to a group, is only initialized on the arrival of the - // first event for that group that has a goal keycode. - FlKeyboardLayout* derived_layout; - - // A static map from keycodes to all layout goals. - // - // It is set up when the handler is initialized and is not changed ever after. - std::unique_ptr> keycode_to_goals; - - // A static map from logical keys to all mandatory layout goals. - // - // It is set up when the handler is initialized and is not changed ever after. - std::unique_ptr> - logical_to_mandatory_goals; - // The channel used by the framework to query the keyboard pressed state. FlMethodChannel* channel; }; G_DEFINE_TYPE(FlKeyboardHandler, fl_keyboard_handler, G_TYPE_OBJECT); -// This is an exact copy of g_ptr_array_find_with_equal_func. Somehow CI -// reports that can not find symbol g_ptr_array_find_with_equal_func, despite -// the fact that it runs well locally. -static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack, - gconstpointer needle, - GEqualFunc equal_func, - guint* index_) { - guint i; - g_return_val_if_fail(haystack != NULL, FALSE); - if (equal_func == NULL) { - equal_func = g_direct_equal; - } - for (i = 0; i < haystack->len; i++) { - if (equal_func(g_ptr_array_index(haystack, i), needle)) { - if (index_ != NULL) { - *index_ = i; - } - return TRUE; - } - } - - return FALSE; -} - -// Compare a #FlKeyboardPendingEvent with the given sequence_id. -static gboolean compare_pending_by_sequence_id(gconstpointer a, - gconstpointer b) { - FlKeyboardPendingEvent* pending = - FL_KEYBOARD_PENDING_EVENT(const_cast(a)); - uint64_t sequence_id = *reinterpret_cast(b); - return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id; -} - -// Compare a #FlKeyboardPendingEvent with the given hash. -static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) { - FlKeyboardPendingEvent* pending = - FL_KEYBOARD_PENDING_EVENT(const_cast(a)); - uint64_t hash = *reinterpret_cast(b); - return fl_keyboard_pending_event_get_hash(pending) == hash; -} - -// Try to remove a pending event from `pending_redispatches` with the target -// hash. -// -// Returns true if the event is found and removed. -static bool fl_keyboard_handler_remove_redispatched(FlKeyboardHandler* self, - uint64_t hash) { - guint result_index; - gboolean found = g_ptr_array_find_with_equal_func1( - self->pending_redispatches, static_cast(&hash), - compare_pending_by_hash, &result_index); - if (found) { - // The removed object is freed due to `pending_redispatches`'s free_func. - g_ptr_array_remove_index_fast(self->pending_redispatches, result_index); - return TRUE; - } else { - return FALSE; - } -} - -// The callback used by a responder after the event was dispatched. -static void responder_handle_event_callback(bool handled, - gpointer user_data_ptr) { - g_return_if_fail(FL_IS_KEYBOARD_HANDLER_USER_DATA(user_data_ptr)); - FlKeyboardHandlerUserData* user_data = - FL_KEYBOARD_HANDLER_USER_DATA(user_data_ptr); - - g_autoptr(FlKeyboardHandler) self = - FL_KEYBOARD_HANDLER(g_weak_ref_get(&user_data->handler)); - if (self == nullptr) { - return; - } - - g_autoptr(FlKeyboardViewDelegate) view_delegate = - FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { - return; - } - - guint result_index = -1; - gboolean found = g_ptr_array_find_with_equal_func1( - self->pending_responds, &user_data->sequence_id, - compare_pending_by_sequence_id, &result_index); - g_return_if_fail(found); - FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT( - g_ptr_array_index(self->pending_responds, result_index)); - g_return_if_fail(pending != nullptr); - fl_keyboard_pending_event_mark_replied(pending, handled); - // All responders have replied. - if (fl_keyboard_pending_event_is_complete(pending)) { - g_object_unref(user_data_ptr); - gpointer removed = - g_ptr_array_remove_index_fast(self->pending_responds, result_index); - g_return_if_fail(removed == pending); - bool should_redispatch = - !fl_keyboard_pending_event_get_any_handled(pending) && - !fl_keyboard_view_delegate_text_filter_key_press( - view_delegate, fl_keyboard_pending_event_get_event(pending)); - if (should_redispatch) { - g_ptr_array_add(self->pending_redispatches, pending); - fl_keyboard_view_delegate_redispatch_event( - view_delegate, - FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending))); - } else { - g_object_unref(pending); - } - } -} - -static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate, - guint keycode, - gint group, - gint level) { - GdkKeymapKey key = {keycode, group, level}; - constexpr int kBmpMax = 0xD7FF; - guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key); - return origin < kBmpMax ? origin : 0xFFFF; -} - -// Make sure that Flutter has derived the layout for the group of the event, -// if the event contains a goal keycode. -static void guarantee_layout(FlKeyboardHandler* self, FlKeyEvent* event) { - g_autoptr(FlKeyboardViewDelegate) view_delegate = - FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { - return; - } - - guint8 group = fl_key_event_get_group(event); - if (fl_keyboard_layout_has_group(self->derived_layout, group)) { - return; - } - if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) == - self->keycode_to_goals->end()) { - return; - } - - // Clone all mandatory goals. Each goal is removed from this cloned map when - // fulfilled, and the remaining ones will be assigned to a default position. - std::map remaining_mandatory_goals = - *self->logical_to_mandatory_goals; - -#ifdef DEBUG_PRINT_LAYOUT - std::string debug_layout_data; - for (uint16_t keycode = 0; keycode < 128; keycode += 1) { - std::vector this_key_clues = { - convert_key_to_char(view_delegate, keycode, group, 0), - convert_key_to_char(view_delegate, keycode, group, 1), // Shift - }; - debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0], - this_key_clues[1]); - } -#endif - - // It's important to only traverse layout goals instead of all keycodes. - // Some key codes outside of the standard keyboard also gives alpha-numeric - // letters, and will therefore take over mandatory goals from standard - // keyboard keys if they come first. Example: French keyboard digit 1. - for (const LayoutGoal& keycode_goal : layout_goals) { - uint16_t keycode = keycode_goal.keycode; - std::vector this_key_clues = { - convert_key_to_char(view_delegate, keycode, group, 0), - convert_key_to_char(view_delegate, keycode, group, 1), // Shift - }; - - // The logical key should be the first available clue from below: - // - // - Mandatory goal, if it matches any clue. This ensures that all alnum - // keys can be found somewhere. - // - US layout, if neither clue of the key is EASCII. This ensures that - // there are no non-latin logical keys. - // - A value derived on the fly from keycode & keyval. - for (uint16_t clue : this_key_clues) { - auto matching_goal = remaining_mandatory_goals.find(clue); - if (matching_goal != remaining_mandatory_goals.end()) { - // Found a key that produces a mandatory char. Use it. - g_return_if_fail(fl_keyboard_layout_get_logical_key( - self->derived_layout, group, keycode) == 0); - fl_keyboard_layout_set_logical_key(self->derived_layout, group, keycode, - clue); - remaining_mandatory_goals.erase(matching_goal); - break; - } - } - bool has_any_eascii = - is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]); - // See if any produced char meets the requirement as a logical key. - if (fl_keyboard_layout_get_logical_key(self->derived_layout, group, - keycode) == 0 && - !has_any_eascii) { - auto found_us_layout = self->keycode_to_goals->find(keycode); - if (found_us_layout != self->keycode_to_goals->end()) { - fl_keyboard_layout_set_logical_key( - self->derived_layout, group, keycode, - found_us_layout->second->logical_key); - } - } - } - - // Ensure all mandatory goals are assigned. - for (const auto mandatory_goal_iter : remaining_mandatory_goals) { - const LayoutGoal* goal = mandatory_goal_iter.second; - fl_keyboard_layout_set_logical_key(self->derived_layout, group, - goal->keycode, goal->logical_key); - } -} - // Returns the keyboard pressed state. static FlMethodResponse* get_keyboard_state(FlKeyboardHandler* self) { g_autoptr(FlValue) result = fl_value_new_map(); @@ -423,15 +70,7 @@ static void fl_keyboard_handler_dispose(GObject* object) { FlKeyboardHandler* self = FL_KEYBOARD_HANDLER(object); g_weak_ref_clear(&self->view_delegate); - - self->keycode_to_goals.reset(); - self->logical_to_mandatory_goals.reset(); - - g_ptr_array_free(self->responder_list, TRUE); - g_ptr_array_set_free_func(self->pending_responds, g_object_unref); - g_ptr_array_free(self->pending_responds, TRUE); - g_ptr_array_free(self->pending_redispatches, TRUE); - g_clear_object(&self->derived_layout); + g_clear_object(&self->channel); G_OBJECT_CLASS(fl_keyboard_handler_parent_class)->dispose(object); } @@ -440,27 +79,7 @@ static void fl_keyboard_handler_class_init(FlKeyboardHandlerClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_keyboard_handler_dispose; } -static void fl_keyboard_handler_init(FlKeyboardHandler* self) { - self->derived_layout = fl_keyboard_layout_new(); - - self->keycode_to_goals = - std::make_unique>(); - self->logical_to_mandatory_goals = - std::make_unique>(); - for (const LayoutGoal& goal : layout_goals) { - (*self->keycode_to_goals)[goal.keycode] = &goal; - if (goal.mandatory) { - (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal; - } - } - - self->responder_list = g_ptr_array_new_with_free_func(g_object_unref); - - self->pending_responds = g_ptr_array_new(); - self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref); - - self->last_sequence_id = 1; -} +static void fl_keyboard_handler_init(FlKeyboardHandler* self) {} FlKeyboardHandler* fl_keyboard_handler_new( FlBinaryMessenger* messenger, @@ -472,27 +91,6 @@ FlKeyboardHandler* fl_keyboard_handler_new( g_weak_ref_init(&self->view_delegate, view_delegate); - // The embedder responder must be added before the channel responder. - g_ptr_array_add( - self->responder_list, - FL_KEY_RESPONDER(fl_key_embedder_responder_new( - [](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, - void* callback_user_data, void* send_key_event_user_data) { - FlKeyboardHandler* self = - FL_KEYBOARD_HANDLER(send_key_event_user_data); - g_autoptr(FlKeyboardViewDelegate) view_delegate = - FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); - if (view_delegate == nullptr) { - return; - } - fl_keyboard_view_delegate_send_key_event( - view_delegate, event, callback, callback_user_data); - }, - self))); - g_ptr_array_add(self->responder_list, - FL_KEY_RESPONDER(fl_key_channel_responder_new( - fl_keyboard_view_delegate_get_messenger(view_delegate)))); - // Setup the flutter/keyboard channel. g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); self->channel = @@ -501,70 +99,3 @@ FlKeyboardHandler* fl_keyboard_handler_new( self, nullptr); return self; } - -gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* self, - FlKeyEvent* event) { - g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE); - g_return_val_if_fail(event != nullptr, FALSE); - - guarantee_layout(self, event); - - uint64_t incoming_hash = fl_key_event_hash(event); - if (fl_keyboard_handler_remove_redispatched(self, incoming_hash)) { - return FALSE; - } - - FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new( - event, ++self->last_sequence_id, self->responder_list->len); - - g_ptr_array_add(self->pending_responds, pending); - FlKeyboardHandlerUserData* user_data = fl_keyboard_handler_user_data_new( - self, fl_keyboard_pending_event_get_sequence_id(pending)); - uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key( - self->derived_layout, fl_key_event_get_group(event), - fl_key_event_get_keycode(event)); - for (guint i = 0; i < self->responder_list->len; i++) { - FlKeyResponder* responder = - FL_KEY_RESPONDER(g_ptr_array_index(self->responder_list, i)); - fl_key_responder_handle_event(responder, event, - responder_handle_event_callback, user_data, - specified_logical_key); - } - - return TRUE; -} - -gboolean fl_keyboard_handler_is_state_clear(FlKeyboardHandler* self) { - g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), FALSE); - return self->pending_responds->len == 0 && - self->pending_redispatches->len == 0; -} - -void fl_keyboard_handler_sync_modifier_if_needed(FlKeyboardHandler* self, - guint state, - double event_time) { - g_return_if_fail(FL_IS_KEYBOARD_HANDLER(self)); - - // The embedder responder is the first element in - // FlKeyboardHandler.responder_list. - FlKeyEmbedderResponder* responder = - FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0)); - fl_key_embedder_responder_sync_modifiers_if_needed(responder, state, - event_time); -} - -GHashTable* fl_keyboard_handler_get_pressed_state(FlKeyboardHandler* self) { - g_return_val_if_fail(FL_IS_KEYBOARD_HANDLER(self), nullptr); - - // The embedder responder is the first element in - // FlKeyboardHandler.responder_list. - FlKeyEmbedderResponder* responder = - FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0)); - return fl_key_embedder_responder_get_pressed_state(responder); -} - -void fl_keyboard_handler_notify_layout_changed(FlKeyboardHandler* self) { - g_return_if_fail(FL_IS_KEYBOARD_HANDLER(self)); - g_clear_object(&self->derived_layout); - self->derived_layout = fl_keyboard_layout_new(); -} diff --git a/shell/platform/linux/fl_keyboard_handler.h b/shell/platform/linux/fl_keyboard_handler.h index 0d551d618f47d..7237755e3c936 100644 --- a/shell/platform/linux/fl_keyboard_handler.h +++ b/shell/platform/linux/fl_keyboard_handler.h @@ -21,17 +21,7 @@ G_DECLARE_FINAL_TYPE(FlKeyboardHandler, /** * FlKeyboardHandler: * - * Processes keyboard events and cooperate with `TextInputHandler`. - * - * A keyboard event goes through a few sections, each can choose to handle the - * event, and only unhandled events can move to the next section: - * - * - Keyboard: Dispatch to the embedder responder and the channel responder - * simultaneously. After both responders have responded (asynchronously), the - * event is considered handled if either responder handles it. - * - Text input: Events are sent to IM filter (usually owned by - * `TextInputHandler`) and are handled synchronously. - * - Redispatching: Events are inserted back to the system for redispatching. + * Provides the channel to receive keyboard requests from the Dart code. */ /** @@ -47,60 +37,6 @@ FlKeyboardHandler* fl_keyboard_handler_new( FlBinaryMessenger* messenger, FlKeyboardViewDelegate* view_delegate); -/** - * fl_keyboard_handler_handle_event: - * @handler: the #FlKeyboardHandler self. - * @event: the event to be dispatched. It is usually a wrap of a GdkEventKey. - * This event will be managed and released by #FlKeyboardHandler. - * - * Make the handler process a system key event. This might eventually send - * messages to the framework, trigger text input effects, or redispatch the - * event back to the system. - */ -gboolean fl_keyboard_handler_handle_event(FlKeyboardHandler* handler, - FlKeyEvent* event); - -/** - * fl_keyboard_handler_is_state_clear: - * @handler: the #FlKeyboardHandler self. - * - * A debug-only method that queries whether the handler's various states are - * cleared, i.e. no pending events for redispatching or for responding. - * - * Returns: true if the handler's various states are cleared. - */ -gboolean fl_keyboard_handler_is_state_clear(FlKeyboardHandler* handler); - -/** - * fl_keyboard_handler_sync_modifier_if_needed: - * @handler: the #FlKeyboardHandler self. - * @state: the state of the modifiers mask. - * @event_time: the time attribute of the incoming GDK event. - * - * If needed, synthesize modifier keys up and down event by comparing their - * current pressing states with the given modifiers mask. - */ -void fl_keyboard_handler_sync_modifier_if_needed(FlKeyboardHandler* handler, - guint state, - double event_time); - -/** - * fl_keyboard_handler_get_pressed_state: - * @handler: the #FlKeyboardHandler self. - * - * Returns the keyboard pressed state. The hash table contains one entry per - * pressed keys, mapping from the logical key to the physical key.* - */ -GHashTable* fl_keyboard_handler_get_pressed_state(FlKeyboardHandler* handler); - -/** - * fl_keyboard_handler_notify_layout_changed: - * @handler: the #FlKeyboardHandler self. - * - * Notify the handler the keyboard layout has changed. - */ -void fl_keyboard_handler_notify_layout_changed(FlKeyboardHandler* handler); - G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_HANDLER_H_ diff --git a/shell/platform/linux/fl_keyboard_handler_test.cc b/shell/platform/linux/fl_keyboard_handler_test.cc index f0e98154c4b02..a4d92784a1f02 100644 --- a/shell/platform/linux/fl_keyboard_handler_test.cc +++ b/shell/platform/linux/fl_keyboard_handler_test.cc @@ -4,143 +4,26 @@ #include "flutter/shell/platform/linux/fl_keyboard_handler.h" -#include -#include - -#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" -#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" #include "flutter/shell/platform/linux/fl_method_codec_private.h" -#include "flutter/shell/platform/linux/key_mapping.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" -#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" -#include "flutter/shell/platform/linux/testing/fl_test.h" #include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" -#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h" -#include "flutter/testing/testing.h" #include "gmock/gmock.h" #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).type, CallRecord::kKeyCallEmbedder); \ - 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(call_records[0].type, CallRecord::kKeyCallEmbedder); \ - EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \ - EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL)); \ - EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR)); \ - EXPECT_EQ(call_records[0].event->synthesized, false); \ - call_records.clear() - -namespace { -using ::flutter::testing::keycodes::kLogicalAltLeft; -using ::flutter::testing::keycodes::kLogicalBracketLeft; -using ::flutter::testing::keycodes::kLogicalComma; -using ::flutter::testing::keycodes::kLogicalControlLeft; -using ::flutter::testing::keycodes::kLogicalDigit1; -using ::flutter::testing::keycodes::kLogicalKeyA; -using ::flutter::testing::keycodes::kLogicalKeyB; -using ::flutter::testing::keycodes::kLogicalKeyM; -using ::flutter::testing::keycodes::kLogicalKeyQ; -using ::flutter::testing::keycodes::kLogicalMetaLeft; -using ::flutter::testing::keycodes::kLogicalMinus; -using ::flutter::testing::keycodes::kLogicalParenthesisRight; -using ::flutter::testing::keycodes::kLogicalSemicolon; -using ::flutter::testing::keycodes::kLogicalShiftLeft; -using ::flutter::testing::keycodes::kLogicalUnderscore; - -using ::flutter::testing::keycodes::kPhysicalAltLeft; -using ::flutter::testing::keycodes::kPhysicalControlLeft; -using ::flutter::testing::keycodes::kPhysicalKeyA; -using ::flutter::testing::keycodes::kPhysicalKeyB; -using ::flutter::testing::keycodes::kPhysicalMetaLeft; -using ::flutter::testing::keycodes::kPhysicalShiftLeft; - -// Hardware key codes. -typedef std::function AsyncKeyCallback; -typedef std::function ChannelCallHandler; -typedef std::function - EmbedderCallHandler; -typedef std::function RedispatchHandler; - -// A type that can record all kinds of effects that the keyboard handler -// triggers. -// -// An instance of `CallRecord` might not have all the fields filled. -typedef struct { - enum { - kKeyCallEmbedder, - kKeyCallChannel, - } type; - - AsyncKeyCallback callback; - std::unique_ptr event; - std::unique_ptr event_character; -} CallRecord; - -// Clone a C-string. -// -// Must be deleted by delete[]. -char* cloneString(const char* source) { - if (source == nullptr) { - return nullptr; - } - size_t charLen = strlen(source); - char* target = new char[charLen + 1]; - strncpy(target, source, charLen + 1); - return target; -} - -constexpr guint16 kKeyCodeKeyA = 0x26u; -constexpr guint16 kKeyCodeKeyB = 0x38u; -constexpr guint16 kKeyCodeKeyM = 0x3au; -constexpr guint16 kKeyCodeDigit1 = 0x0au; -constexpr guint16 kKeyCodeMinus = 0x14u; -constexpr guint16 kKeyCodeSemicolon = 0x2fu; -constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u; - -static constexpr char kKeyEventChannelName[] = "flutter/keyevent"; static constexpr char kKeyboardChannelName[] = "flutter/keyboard"; static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState"; static constexpr uint64_t kMockPhysicalKey = 42; static constexpr uint64_t kMockLogicalKey = 42; -// All key clues for a keyboard layout. -// -// The index is (keyCode * 2 + hasShift), where each value is the character for -// this key (GTK only supports UTF-16.) Since the maximum keycode of interest -// is 128, it has a total of 256 entries.. -typedef std::array MockGroupLayoutData; -typedef std::vector MockLayoutData; - -extern const MockLayoutData kLayoutUs; -extern const MockLayoutData kLayoutRussian; -extern const MockLayoutData kLayoutFrench; - G_BEGIN_DECLS -G_DECLARE_FINAL_TYPE(FlMockViewDelegate, - fl_mock_view_delegate, +G_DECLARE_FINAL_TYPE(FlMockKeyboardHandlerDelegate, + fl_mock_keyboard_handler_delegate, FL, - MOCK_VIEW_DELEGATE, + MOCK_KEYBOARD_HANDLER_DELEGATE, GObject); -G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger, - fl_mock_key_binary_messenger, - FL, - MOCK_KEY_BINARY_MESSENGER, - GObject) - G_END_DECLS MATCHER_P(MethodSuccessResponse, result, "") { @@ -156,243 +39,26 @@ MATCHER_P(MethodSuccessResponse, result, "") { return false; } -/***** FlMockKeyBinaryMessenger *****/ -/* Mock a binary messenger that only processes messages from the embedding on - * the key event channel, and does so according to the callback set by - * fl_mock_key_binary_messenger_set_callback_handler */ - -struct _FlMockKeyBinaryMessenger { - GObject parent_instance; -}; - -struct FlMockKeyBinaryMessengerPrivate { - ChannelCallHandler callback_handler; -}; - -static void fl_mock_key_binary_messenger_iface_init( - FlBinaryMessengerInterface* iface); - -G_DEFINE_TYPE_WITH_CODE( - FlMockKeyBinaryMessenger, - fl_mock_key_binary_messenger, - G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(), - fl_mock_key_binary_messenger_iface_init); - G_ADD_PRIVATE(FlMockKeyBinaryMessenger)) - -#define FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(obj) \ - static_cast( \ - fl_mock_key_binary_messenger_get_instance_private( \ - FL_MOCK_KEY_BINARY_MESSENGER(obj))) - -static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) { - FlMockKeyBinaryMessengerPrivate* priv = - FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self); - new (priv) FlMockKeyBinaryMessengerPrivate(); -} - -static void fl_mock_key_binary_messenger_dispose(GObject* object) { - FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(object) - ->~FlMockKeyBinaryMessengerPrivate(); - - G_OBJECT_CLASS(fl_mock_key_binary_messenger_parent_class)->dispose(object); -} - -static void fl_mock_key_binary_messenger_class_init( - FlMockKeyBinaryMessengerClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_mock_key_binary_messenger_dispose; -} - -static void fl_mock_key_binary_messenger_send_on_channel( - FlBinaryMessenger* messenger, - const gchar* channel, - GBytes* message, - GCancellable* cancellable, - GAsyncReadyCallback callback, - gpointer user_data) { - FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger); - - if (callback != nullptr) { - EXPECT_STREQ(channel, kKeyEventChannelName); - FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self)->callback_handler( - [self, cancellable, callback, user_data](bool handled) { - g_autoptr(GTask) task = - g_task_new(self, cancellable, callback, user_data); - g_autoptr(FlValue) result = fl_value_new_map(); - fl_value_set_string_take(result, "handled", - fl_value_new_bool(handled)); - g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); - g_autoptr(GError) error = nullptr; - GBytes* data = fl_message_codec_encode_message( - FL_MESSAGE_CODEC(codec), result, &error); - - g_task_return_pointer( - task, data, reinterpret_cast(g_bytes_unref)); - }); - } -} - -static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish( - FlBinaryMessenger* messenger, - GAsyncResult* result, - GError** error) { - return static_cast(g_task_propagate_pointer(G_TASK(result), error)); -} - -static void fl_mock_binary_messenger_resize_channel( - FlBinaryMessenger* messenger, - const gchar* channel, - int64_t new_size) { - // Mock implementation. Do nothing. -} - -static void fl_mock_binary_messenger_set_warns_on_channel_overflow( - FlBinaryMessenger* messenger, - const gchar* channel, - bool warns) { - // Mock implementation. Do nothing. -} - -static void fl_mock_key_binary_messenger_iface_init( - FlBinaryMessengerInterface* iface) { - iface->set_message_handler_on_channel = - [](FlBinaryMessenger* messenger, const gchar* channel, - FlBinaryMessengerMessageHandler handler, gpointer user_data, - GDestroyNotify destroy_notify) { - EXPECT_STREQ(channel, kKeyEventChannelName); - // No need to mock. The key event channel expects no incoming messages - // from the framework. - }; - iface->send_response = [](FlBinaryMessenger* messenger, - FlBinaryMessengerResponseHandle* response_handle, - GBytes* response, GError** error) -> gboolean { - // The key event channel expects no incoming messages from the framework, - // hence no responses either. - g_return_val_if_reached(TRUE); - return TRUE; - }; - iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel; - iface->send_on_channel_finish = - fl_mock_key_binary_messenger_send_on_channel_finish; - iface->resize_channel = fl_mock_binary_messenger_resize_channel; - iface->set_warns_on_channel_overflow = - fl_mock_binary_messenger_set_warns_on_channel_overflow; -} - -static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() { - FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER( - g_object_new(fl_mock_key_binary_messenger_get_type(), NULL)); - - // Added to stop compiler complaining about an unused function. - FL_IS_MOCK_KEY_BINARY_MESSENGER(self); - - return self; -} - -static void fl_mock_key_binary_messenger_set_callback_handler( - FlMockKeyBinaryMessenger* self, - ChannelCallHandler handler) { - FL_MOCK_KEY_BINARY_MESSENGER_GET_PRIVATE(self)->callback_handler = - std::move(handler); -} - -/***** FlMockViewDelegate *****/ - -struct _FlMockViewDelegate { +struct _FlMockKeyboardHandlerDelegate { GObject parent_instance; }; -struct FlMockViewDelegatePrivate { - FlMockKeyBinaryMessenger* messenger; - EmbedderCallHandler embedder_handler; - bool text_filter_result; - RedispatchHandler redispatch_handler; - const MockLayoutData* layout_data; -}; - -static void fl_mock_view_keyboard_delegate_iface_init( +static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init( FlKeyboardViewDelegateInterface* iface); G_DEFINE_TYPE_WITH_CODE( - FlMockViewDelegate, - fl_mock_view_delegate, + FlMockKeyboardHandlerDelegate, + fl_mock_keyboard_handler_delegate, G_TYPE_OBJECT, - G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(), - fl_mock_view_keyboard_delegate_iface_init); - G_ADD_PRIVATE(FlMockViewDelegate)) - -#define FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(obj) \ - static_cast( \ - fl_mock_view_delegate_get_instance_private(FL_MOCK_VIEW_DELEGATE(obj))) - -static void fl_mock_view_delegate_init(FlMockViewDelegate* self) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self); - new (priv) FlMockViewDelegatePrivate(); -} - -static void fl_mock_view_delegate_dispose(GObject* object) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(object); - - FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(object)->~FlMockViewDelegatePrivate(); - g_clear_object(&priv->messenger); - - G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object); -} - -static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) { - G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose; -} - -static void fl_mock_view_keyboard_send_key_event( - FlKeyboardViewDelegate* view_delegate, - const FlutterKeyEvent* event, - FlutterKeyEventCallback callback, - void* user_data) { - FlMockViewDelegatePrivate* priv = - FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate); - priv->embedder_handler(event, [callback, user_data](bool handled) { - if (callback != nullptr) { - callback(handled, user_data); - } - }); -} - -static gboolean fl_mock_view_keyboard_text_filter_key_press( - FlKeyboardViewDelegate* view_delegate, - FlKeyEvent* event) { - FlMockViewDelegatePrivate* priv = - FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate); - return priv->text_filter_result; -} + G_IMPLEMENT_INTERFACE( + fl_keyboard_view_delegate_get_type(), + fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init)) -static FlBinaryMessenger* fl_mock_view_keyboard_get_messenger( - FlKeyboardViewDelegate* view_delegate) { - FlMockViewDelegatePrivate* priv = - FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate); - return FL_BINARY_MESSENGER(priv->messenger); -} +static void fl_mock_keyboard_handler_delegate_init( + FlMockKeyboardHandlerDelegate* self) {} -static void fl_mock_view_keyboard_redispatch_event( - FlKeyboardViewDelegate* view_delegate, - FlKeyEvent* event) { - FlMockViewDelegatePrivate* priv = - FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_delegate); - if (priv->redispatch_handler) { - priv->redispatch_handler(event); - } -} - -static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate, - const GdkKeymapKey* key) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(delegate); - guint8 group = static_cast(key->group); - EXPECT_LT(group, priv->layout_data->size()); - const MockGroupLayoutData* group_layout = (*priv->layout_data)[group]; - EXPECT_TRUE(group_layout != nullptr); - EXPECT_TRUE(key->level == 0 || key->level == 1); - bool shift = key->level == 1; - return (*group_layout)[key->keycode * 2 + shift]; -} +static void fl_mock_keyboard_handler_delegate_class_init( + FlMockKeyboardHandlerDelegateClass* klass) {} static GHashTable* fl_mock_view_keyboard_get_keyboard_state( FlKeyboardViewDelegate* view_delegate) { @@ -403,620 +69,27 @@ static GHashTable* fl_mock_view_keyboard_get_keyboard_state( return result; } -static void fl_mock_view_keyboard_delegate_iface_init( +static void fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init( FlKeyboardViewDelegateInterface* iface) { - iface->send_key_event = fl_mock_view_keyboard_send_key_event; - iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press; - iface->get_messenger = fl_mock_view_keyboard_get_messenger; - iface->redispatch_event = fl_mock_view_keyboard_redispatch_event; - iface->lookup_key = fl_mock_view_keyboard_lookup_key; iface->get_keyboard_state = fl_mock_view_keyboard_get_keyboard_state; } -static FlMockViewDelegate* fl_mock_view_delegate_new() { - FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE( - g_object_new(fl_mock_view_delegate_get_type(), nullptr)); +static FlMockKeyboardHandlerDelegate* fl_mock_keyboard_handler_delegate_new() { + FlMockKeyboardHandlerDelegate* self = FL_MOCK_KEYBOARD_HANDLER_DELEGATE( + g_object_new(fl_mock_keyboard_handler_delegate_get_type(), nullptr)); // Added to stop compiler complaining about an unused function. - FL_IS_MOCK_VIEW_DELEGATE(self); - - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self); - priv->messenger = fl_mock_key_binary_messenger_new(); + FL_IS_MOCK_KEYBOARD_HANDLER_DELEGATE(self); return self; } -static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self, - EmbedderCallHandler handler) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self); - priv->embedder_handler = std::move(handler); -} - -static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self, - bool result) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self); - priv->text_filter_result = result; -} - -static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self, - RedispatchHandler handler) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self); - priv->redispatch_handler = std::move(handler); -} - -static void fl_mock_view_set_layout(FlMockViewDelegate* self, - const MockLayoutData* layout) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(self); - priv->layout_data = layout; -} - -/***** End FlMockViewDelegate *****/ - -class KeyboardTester { - public: - KeyboardTester() { - ::testing::NiceMock messenger; - - view_ = fl_mock_view_delegate_new(); - respondToEmbedderCallsWith(false); - respondToChannelCallsWith(false); - respondToTextInputWith(false); - setLayout(kLayoutUs); - - handler_ = - fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(view_)); - } - - ~KeyboardTester() { - g_clear_object(&view_); - g_clear_object(&handler_); - g_clear_pointer(&redispatched_events_, g_ptr_array_unref); - } - - FlKeyboardHandler* handler() { return handler_; } - - // Block until all GdkMainLoop messages are processed, which is basically - // used only for channel messages. - void flushChannelMessages() { - GMainLoop* loop = g_main_loop_new(nullptr, 0); - g_idle_add(_flushChannelMessagesCb, loop); - g_main_loop_run(loop); - } - - // Dispatch each of the given events, expect their results to be false - // (unhandled), and clear the event array. - // - // Returns the number of events redispatched. If any result is unexpected - // (handled), return a minus number `-x` instead, where `x` is the index of - // the first unexpected redispatch. - int redispatchEventsAndClear(GPtrArray* events) { - guint event_count = events->len; - int first_error = -1; - during_redispatch_ = true; - for (guint event_id = 0; event_id < event_count; event_id += 1) { - FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id)); - bool handled = fl_keyboard_handler_handle_event(handler_, event); - EXPECT_FALSE(handled); - if (handled) { - first_error = first_error == -1 ? event_id : first_error; - } - } - during_redispatch_ = false; - g_ptr_array_set_size(events, 0); - return first_error < 0 ? event_count : -first_error; - } - - void respondToEmbedderCallsWith(bool response) { - fl_mock_view_set_embedder_handler( - view_, [response, this](const FlutterKeyEvent* event, - const AsyncKeyCallback& callback) { - EXPECT_FALSE(during_redispatch_); - callback(response); - }); - } - - void recordEmbedderCallsTo(std::vector& storage) { - fl_mock_view_set_embedder_handler( - view_, [&storage, this](const FlutterKeyEvent* event, - AsyncKeyCallback callback) { - EXPECT_FALSE(during_redispatch_); - auto new_event = std::make_unique(*event); - char* new_event_character = cloneString(event->character); - new_event->character = new_event_character; - storage.push_back(CallRecord{ - .type = CallRecord::kKeyCallEmbedder, - .callback = std::move(callback), - .event = std::move(new_event), - .event_character = std::unique_ptr(new_event_character), - }); - }); - } - - void respondToEmbedderCallsWithAndRecordsTo( - bool response, - std::vector& storage) { - fl_mock_view_set_embedder_handler( - view_, [&storage, response, this](const FlutterKeyEvent* event, - const AsyncKeyCallback& callback) { - EXPECT_FALSE(during_redispatch_); - auto new_event = std::make_unique(*event); - char* new_event_character = cloneString(event->character); - new_event->character = new_event_character; - storage.push_back(CallRecord{ - .type = CallRecord::kKeyCallEmbedder, - .event = std::move(new_event), - .event_character = std::unique_ptr(new_event_character), - }); - callback(response); - }); - } - - void respondToChannelCallsWith(bool response) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_); - - fl_mock_key_binary_messenger_set_callback_handler( - priv->messenger, [response, this](const AsyncKeyCallback& callback) { - EXPECT_FALSE(during_redispatch_); - callback(response); - }); - } - - void recordChannelCallsTo(std::vector& storage) { - FlMockViewDelegatePrivate* priv = FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(view_); - - fl_mock_key_binary_messenger_set_callback_handler( - priv->messenger, [&storage, this](AsyncKeyCallback callback) { - EXPECT_FALSE(during_redispatch_); - storage.push_back(CallRecord{ - .type = CallRecord::kKeyCallChannel, - .callback = std::move(callback), - }); - }); - } - - void respondToTextInputWith(bool response) { - fl_mock_view_set_text_filter_result(view_, response); - } - - void recordRedispatchedEventsTo(GPtrArray* storage) { - redispatched_events_ = g_ptr_array_ref(storage); - fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) { - g_ptr_array_add(redispatched_events_, g_object_ref(key)); - }); - } - - void setLayout(const MockLayoutData& layout) { - fl_mock_view_set_layout(view_, &layout); - if (handler_ != nullptr) { - fl_keyboard_handler_notify_layout_changed(handler_); - } - } - - private: - FlMockViewDelegate* view_; - FlKeyboardHandler* handler_ = nullptr; - GPtrArray* redispatched_events_ = nullptr; - bool during_redispatch_ = false; - - static gboolean _flushChannelMessagesCb(gpointer data) { - g_autoptr(GMainLoop) loop = reinterpret_cast(data); - g_main_loop_quit(loop); - return FALSE; - } -}; - -// Make sure that the keyboard can be disposed without crashes when there are -// unresolved pending events. -TEST(FlKeyboardHandlerTest, DisposeWithUnresolvedPends) { - KeyboardTester tester; - std::vector call_records; - - // Record calls so that they aren't responded. - tester.recordEmbedderCallsTo(call_records); - g_autoptr(FlKeyEvent) event1 = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_keyboard_handler_handle_event(tester.handler(), event1); - - tester.respondToEmbedderCallsWith(true); - g_autoptr(FlKeyEvent) event2 = fl_key_event_new( - 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_keyboard_handler_handle_event(tester.handler(), event2); - - tester.flushChannelMessages(); - - // Passes if the cleanup does not crash. -} - -TEST(FlKeyboardHandlerTest, SingleDelegateWithAsyncResponds) { - KeyboardTester tester; - std::vector call_records; - g_autoptr(GPtrArray) redispatched = - g_ptr_array_new_with_free_func(g_object_unref); - - gboolean handler_handled = false; - - /// Test 1: One event that is handled by the framework - tester.recordEmbedderCallsTo(call_records); - tester.recordRedispatchedEventsTo(redispatched); - - // Dispatch a key event - g_autoptr(FlKeyEvent) event1 = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, - kLogicalKeyA, "a", false); - - call_records[0].callback(true); - tester.flushChannelMessages(); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); - call_records.clear(); - - /// Test 2: Two events that are unhandled by the framework - g_autoptr(FlKeyEvent) event2 = fl_key_event_new( - 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, - kLogicalKeyA, nullptr, false); - - // Dispatch another key event - g_autoptr(FlKeyEvent) event3 = fl_key_event_new( - 0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event3); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_EQ(call_records.size(), 2u); - EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB, - kLogicalKeyB, "b", false); - - // Resolve the second event first to test out-of-order response - call_records[1].callback(false); - EXPECT_EQ(redispatched->len, 1u); - EXPECT_EQ( - fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))), - 0x62u); - call_records[0].callback(false); - tester.flushChannelMessages(); - EXPECT_EQ(redispatched->len, 2u); - EXPECT_EQ( - fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))), - 0x61u); - - EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler())); - call_records.clear(); - - // Resolve redispatches - EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2); - tester.flushChannelMessages(); - EXPECT_EQ(call_records.size(), 0u); - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); - - /// Test 3: Dispatch the same event again to ensure that prevention from - /// redispatching only works once. - g_autoptr(FlKeyEvent) event4 = fl_key_event_new( - 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event4); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_EQ(call_records.size(), 1u); - - call_records[0].callback(true); - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); -} - -TEST(FlKeyboardHandlerTest, SingleDelegateWithSyncResponds) { - KeyboardTester tester; - gboolean handler_handled = false; - std::vector call_records; - g_autoptr(GPtrArray) redispatched = - g_ptr_array_new_with_free_func(g_object_unref); - - /// Test 1: One event that is handled by the framework - tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records); - tester.recordRedispatchedEventsTo(redispatched); - - // Dispatch a key event - g_autoptr(FlKeyEvent) event1 = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, - kLogicalKeyA, "a", false); - EXPECT_EQ(redispatched->len, 0u); - call_records.clear(); - - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); - g_ptr_array_set_size(redispatched, 0); - - /// Test 2: An event unhandled by the framework - tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records); - g_autoptr(FlKeyEvent) event2 = fl_key_event_new( - 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, - kLogicalKeyA, nullptr, false); - EXPECT_EQ(redispatched->len, 1u); - call_records.clear(); - - EXPECT_FALSE(fl_keyboard_handler_is_state_clear(tester.handler())); - - EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); - EXPECT_EQ(call_records.size(), 0u); - - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); -} - -TEST(FlKeyboardHandlerTest, WithTwoAsyncDelegates) { - KeyboardTester tester; - std::vector call_records; - g_autoptr(GPtrArray) redispatched = - g_ptr_array_new_with_free_func(g_object_unref); - - gboolean handler_handled = false; - - tester.recordEmbedderCallsTo(call_records); - tester.recordChannelCallsTo(call_records); - tester.recordRedispatchedEventsTo(redispatched); - - /// Test 1: One delegate responds true, the other false - - g_autoptr(FlKeyEvent) event1 = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1); - - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_EQ(call_records.size(), 2u); - - EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); - EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); - - call_records[0].callback(true); - call_records[1].callback(false); - tester.flushChannelMessages(); - EXPECT_EQ(redispatched->len, 0u); - - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); - call_records.clear(); - - /// Test 2: All delegates respond false - g_autoptr(FlKeyEvent) event2 = fl_key_event_new( - 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2); - - EXPECT_EQ(handler_handled, true); - EXPECT_EQ(redispatched->len, 0u); - EXPECT_EQ(call_records.size(), 2u); - - EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); - EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); - - call_records[0].callback(false); - call_records[1].callback(false); - - call_records.clear(); - - // Resolve redispatch - tester.flushChannelMessages(); - EXPECT_EQ(redispatched->len, 1u); - EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); - EXPECT_EQ(call_records.size(), 0u); - - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); -} - -TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsFalse) { - KeyboardTester tester; - g_autoptr(GPtrArray) redispatched = - g_ptr_array_new_with_free_func(g_object_unref); - gboolean handler_handled = false; - tester.recordRedispatchedEventsTo(redispatched); - tester.respondToTextInputWith(false); - - // Dispatch a key event. - g_autoptr(FlKeyEvent) event = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - // The event was redispatched because no one handles it. - EXPECT_EQ(redispatched->len, 1u); - - // Resolve redispatched event. - EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); - - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); -} - -TEST(FlKeyboardHandlerTest, TextInputHandlerReturnsTrue) { - KeyboardTester tester; - g_autoptr(GPtrArray) redispatched = - g_ptr_array_new_with_free_func(g_object_unref); - gboolean handler_handled = false; - tester.recordRedispatchedEventsTo(redispatched); - tester.respondToTextInputWith(true); - - // Dispatch a key event. - g_autoptr(FlKeyEvent) event = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event); - tester.flushChannelMessages(); - EXPECT_EQ(handler_handled, true); - // The event was not redispatched because handler handles it. - EXPECT_EQ(redispatched->len, 0u); - - EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler())); -} - -TEST(FlKeyboardHandlerTest, CorrectLogicalKeyForLayouts) { - KeyboardTester tester; - - std::vector call_records; - tester.recordEmbedderCallsTo(call_records); - - auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) { - g_autoptr(FlKeyEvent) event1 = fl_key_event_new( - 0, TRUE, keycode, keyval, static_cast(0), group); - fl_keyboard_handler_handle_event(tester.handler(), event1); - g_autoptr(FlKeyEvent) event2 = fl_key_event_new( - 0, FALSE, keycode, keyval, static_cast(0), group); - fl_keyboard_handler_handle_event(tester.handler(), event2); - }; - - /* US keyboard layout */ - - sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA - VERIFY_DOWN(kLogicalKeyA, "a"); - - sendTap(kKeyCodeKeyA, GDK_KEY_A, 0); // Shift-KeyA - VERIFY_DOWN(kLogicalKeyA, "A"); - - sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 - VERIFY_DOWN(kLogicalDigit1, "1"); - - sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0); // Shift-Digit1 - VERIFY_DOWN(kLogicalDigit1, "!"); - - sendTap(kKeyCodeMinus, GDK_KEY_minus, 0); // Minus - VERIFY_DOWN(kLogicalMinus, "-"); - - sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0); // Shift-Minus - VERIFY_DOWN(kLogicalUnderscore, "_"); - - /* French keyboard layout, group 3, which is when the input method is showing - * "Fr" */ - - tester.setLayout(kLayoutFrench); - - sendTap(kKeyCodeKeyA, GDK_KEY_q, 3); // KeyA - VERIFY_DOWN(kLogicalKeyQ, "q"); - - sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3); // Shift-KeyA - VERIFY_DOWN(kLogicalKeyQ, "Q"); - - sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3); // ; but prints M - VERIFY_DOWN(kLogicalKeyM, "m"); - - sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3); // M but prints , - VERIFY_DOWN(kLogicalComma, ","); - - sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3); // Digit1 - VERIFY_DOWN(kLogicalDigit1, "&"); - - sendTap(kKeyCodeDigit1, GDK_KEY_1, 3); // Shift-Digit1 - VERIFY_DOWN(kLogicalDigit1, "1"); - - sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3); // Minus - VERIFY_DOWN(kLogicalParenthesisRight, ")"); - - sendTap(kKeyCodeMinus, GDK_KEY_degree, 3); // Shift-Minus - VERIFY_DOWN(static_cast(L'°'), "°"); - - /* French keyboard layout, group 0, which is pressing the "extra key for - * triggering input method" key once after switching to French IME. */ - - sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA - VERIFY_DOWN(kLogicalKeyA, "a"); - - sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 - VERIFY_DOWN(kLogicalDigit1, "1"); - - /* Russian keyboard layout, group 2 */ - tester.setLayout(kLayoutRussian); - - sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2); // KeyA - VERIFY_DOWN(kLogicalKeyA, "ф"); - - sendTap(kKeyCodeDigit1, GDK_KEY_1, 2); // Shift-Digit1 - VERIFY_DOWN(kLogicalDigit1, "1"); - - sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2); - VERIFY_DOWN(kLogicalBracketLeft, "х"); - - /* Russian keyboard layout, group 0 */ - sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA - VERIFY_DOWN(kLogicalKeyA, "a"); - - sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0); - VERIFY_DOWN(kLogicalBracketLeft, "["); -} - -TEST(FlKeyboardHandlerTest, SynthesizeModifiersIfNeeded) { - KeyboardTester tester; - std::vector call_records; - tester.recordEmbedderCallsTo(call_records); - - auto verifyModifierIsSynthesized = [&](GdkModifierType mask, - uint64_t physical, uint64_t logical) { - // Modifier is pressed. - guint state = mask; - fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1000); - EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical, - logical, NULL, true); - // Modifier is released. - state = state ^ mask; - fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1001); - EXPECT_EQ(call_records.size(), 2u); - EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical, - NULL, true); - call_records.clear(); - }; - - // No modifiers pressed. - guint state = 0; - fl_keyboard_handler_sync_modifier_if_needed(tester.handler(), state, 1000); - EXPECT_EQ(call_records.size(), 0u); - call_records.clear(); - - // Press and release each modifier once. - verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft, - kLogicalControlLeft); - verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft, - kLogicalMetaLeft); - verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft); - verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft, - kLogicalShiftLeft); -} - -TEST(FlKeyboardHandlerTest, GetPressedState) { - KeyboardTester tester; - tester.respondToTextInputWith(true); - - // Dispatch a key event. - g_autoptr(FlKeyEvent) event = fl_key_event_new( - 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); - fl_keyboard_handler_handle_event(tester.handler(), event); - - GHashTable* pressedState = - fl_keyboard_handler_get_pressed_state(tester.handler()); - EXPECT_EQ(g_hash_table_size(pressedState), 1u); - - gpointer physical_key = - g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA)); - EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA); -} - TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) { ::testing::NiceMock messenger; g_autoptr(FlKeyboardHandler) handler = fl_keyboard_handler_new( - messenger, FL_KEYBOARD_VIEW_DELEGATE(fl_mock_view_delegate_new())); + messenger, + FL_KEYBOARD_VIEW_DELEGATE(fl_mock_keyboard_handler_delegate_new())); EXPECT_NE(handler, nullptr); g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); @@ -1034,193 +107,3 @@ TEST(FlKeyboardHandlerTest, KeyboardChannelGetPressedState) { messenger.ReceiveMessage(kKeyboardChannelName, message); } - -// The following layout data is generated using DEBUG_PRINT_LAYOUT. - -const MockGroupLayoutData kLayoutUs0{{ - // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 - 0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 - 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c - 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 - 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 - 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 - 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c - 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 - 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 - 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 - 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c - 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 - 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 - 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 - 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c - 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 - 0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x64 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 - 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c -}}; - -const MockGroupLayoutData kLayoutRussian0{ - // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 - 0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 - 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c - 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 - 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 - 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 - 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c - 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 - 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 - 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 - 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c - 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 - 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 - 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 - 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c - 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 - 0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c - 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 - 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 - 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c -}; - -const MockGroupLayoutData kLayoutRussian2{{ - // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 - 0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022, // 0x08 - 0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a, // 0x0c - 0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 - 0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000, // 0x14 - 0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb, // 0x18 - 0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb, // 0x1c - 0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff, // 0x20 - 0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9, // 0x24 - 0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2, // 0x28 - 0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6, // 0x2c - 0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f, // 0x30 - 0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed, // 0x34 - 0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2, // 0x38 - 0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 - 0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff, // 0x5c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x64 - 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000, // 0x68 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, // 0x78 - 0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x7c -}}; - -const MockGroupLayoutData kLayoutFrench0 = { - // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 - 0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 - 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c - 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 - 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 - 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 - 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c - 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 - 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 - 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 - 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c - 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 - 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 - 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 - 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c - 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 - 0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c - 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 - 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 - 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c -}; - -const MockGroupLayoutData kLayoutFrench3 = { - // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 - 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 - 0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032, // 0x08 - 0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036, // 0x0c - 0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030, // 0x10 - 0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041, // 0x14 - 0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 - 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c - 0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3, // 0x20 - 0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053, // 0x24 - 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 - 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d, // 0x2c - 0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5, // 0x30 - 0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 - 0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e, // 0x38 - 0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 - 0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c - 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 - 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 - 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, // 0x78 - 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x7c -}; - -const MockLayoutData kLayoutUs{&kLayoutUs0}; -const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr, - &kLayoutRussian2}; -const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr, - &kLayoutFrench3}; - -} // namespace diff --git a/shell/platform/linux/fl_keyboard_manager.cc b/shell/platform/linux/fl_keyboard_manager.cc new file mode 100644 index 0000000000000..869d23bb030c4 --- /dev/null +++ b/shell/platform/linux/fl_keyboard_manager.cc @@ -0,0 +1,510 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux/fl_keyboard_manager.h" + +#include +#include +#include +#include + +#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" +#include "flutter/shell/platform/linux/fl_keyboard_pending_event.h" +#include "flutter/shell/platform/linux/key_mapping.h" + +// Turn on this flag to print complete layout data when switching IMEs. The data +// is used in unit tests. +#define DEBUG_PRINT_LAYOUT + +/* Declarations of private classes */ + +G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData, + fl_keyboard_manager_user_data, + FL, + KEYBOARD_MANAGER_USER_DATA, + GObject); + +/* End declarations */ + +namespace { + +static bool is_eascii(uint16_t character) { + return character < 256; +} + +#ifdef DEBUG_PRINT_LAYOUT +// Prints layout entries that will be parsed by `MockLayoutData`. +void debug_format_layout_data(std::string& debug_layout_data, + uint16_t keycode, + uint16_t clue1, + uint16_t clue2) { + if (keycode % 4 == 0) { + debug_layout_data.append(" "); + } + + constexpr int kBufferSize = 30; + char buffer[kBufferSize]; + buffer[0] = 0; + buffer[kBufferSize - 1] = 0; + + snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2); + debug_layout_data.append(buffer); + + if (keycode % 4 == 3) { + snprintf(buffer, kBufferSize, " // 0x%02x", keycode); + debug_layout_data.append(buffer); + } +} +#endif + +} // namespace + +/* Define FlKeyboardManagerUserData */ + +/** + * FlKeyboardManagerUserData: + * The user_data used when #FlKeyboardManager sends event to + * responders. + */ + +struct _FlKeyboardManagerUserData { + GObject parent_instance; + + // The owner manager. + GWeakRef manager; + uint64_t sequence_id; +}; + +G_DEFINE_TYPE(FlKeyboardManagerUserData, + fl_keyboard_manager_user_data, + G_TYPE_OBJECT) + +static void fl_keyboard_manager_user_data_dispose(GObject* object) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(object)); + FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(object); + + g_weak_ref_clear(&self->manager); + + G_OBJECT_CLASS(fl_keyboard_manager_user_data_parent_class)->dispose(object); +} + +static void fl_keyboard_manager_user_data_class_init( + FlKeyboardManagerUserDataClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_user_data_dispose; +} + +static void fl_keyboard_manager_user_data_init( + FlKeyboardManagerUserData* self) {} + +// Creates a new FlKeyboardManagerUserData private class with all information. +static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new( + FlKeyboardManager* manager, + uint64_t sequence_id) { + FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA( + g_object_new(fl_keyboard_manager_user_data_get_type(), nullptr)); + + g_weak_ref_init(&self->manager, manager); + self->sequence_id = sequence_id; + return self; +} + +/* Define FlKeyboardManager */ + +struct _FlKeyboardManager { + GObject parent_instance; + + GWeakRef view_delegate; + + // An array of #FlKeyResponder. Elements are added with + // #fl_keyboard_manager_add_responder immediately after initialization and are + // automatically released on dispose. + GPtrArray* responder_list; + + // An array of #FlKeyboardPendingEvent. + // + // Its elements are *not* unreferenced when removed. When FlKeyboardManager is + // disposed, this array will be set with a free_func so that the elements are + // unreferenced when removed. + GPtrArray* pending_responds; + + // An array of #FlKeyboardPendingEvent. + // + // Its elements are unreferenced when removed. + GPtrArray* pending_redispatches; + + // The last sequence ID used. Increased by 1 by every use. + uint64_t last_sequence_id; + + // Record the derived layout. + // + // It is cleared when the platform reports a layout switch. Each entry, + // which corresponds to a group, is only initialized on the arrival of the + // first event for that group that has a goal keycode. + FlKeyboardLayout* derived_layout; + + // A static map from keycodes to all layout goals. + // + // It is set up when the manager is initialized and is not changed ever after. + std::unique_ptr> keycode_to_goals; + + // A static map from logical keys to all mandatory layout goals. + // + // It is set up when the manager is initialized and is not changed ever after. + std::unique_ptr> + logical_to_mandatory_goals; +}; + +G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT); + +// This is an exact copy of g_ptr_array_find_with_equal_func. Somehow CI +// reports that can not find symbol g_ptr_array_find_with_equal_func, despite +// the fact that it runs well locally. +static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack, + gconstpointer needle, + GEqualFunc equal_func, + guint* index_) { + guint i; + g_return_val_if_fail(haystack != NULL, FALSE); + if (equal_func == NULL) { + equal_func = g_direct_equal; + } + for (i = 0; i < haystack->len; i++) { + if (equal_func(g_ptr_array_index(haystack, i), needle)) { + if (index_ != NULL) { + *index_ = i; + } + return TRUE; + } + } + + return FALSE; +} + +// Compare a #FlKeyboardPendingEvent with the given sequence_id. +static gboolean compare_pending_by_sequence_id(gconstpointer a, + gconstpointer b) { + FlKeyboardPendingEvent* pending = + FL_KEYBOARD_PENDING_EVENT(const_cast(a)); + uint64_t sequence_id = *reinterpret_cast(b); + return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id; +} + +// Compare a #FlKeyboardPendingEvent with the given hash. +static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) { + FlKeyboardPendingEvent* pending = + FL_KEYBOARD_PENDING_EVENT(const_cast(a)); + uint64_t hash = *reinterpret_cast(b); + return fl_keyboard_pending_event_get_hash(pending) == hash; +} + +// Try to remove a pending event from `pending_redispatches` with the target +// hash. +// +// Returns true if the event is found and removed. +static bool fl_keyboard_manager_remove_redispatched(FlKeyboardManager* self, + uint64_t hash) { + guint result_index; + gboolean found = g_ptr_array_find_with_equal_func1( + self->pending_redispatches, static_cast(&hash), + compare_pending_by_hash, &result_index); + if (found) { + // The removed object is freed due to `pending_redispatches`'s free_func. + g_ptr_array_remove_index_fast(self->pending_redispatches, result_index); + return TRUE; + } else { + return FALSE; + } +} + +// The callback used by a responder after the event was dispatched. +static void responder_handle_event_callback(bool handled, + gpointer user_data_ptr) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER_USER_DATA(user_data_ptr)); + FlKeyboardManagerUserData* user_data = + FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr); + + g_autoptr(FlKeyboardManager) self = + FL_KEYBOARD_MANAGER(g_weak_ref_get(&user_data->manager)); + if (self == nullptr) { + return; + } + + g_autoptr(FlKeyboardViewDelegate) view_delegate = + FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); + if (view_delegate == nullptr) { + return; + } + + guint result_index = -1; + gboolean found = g_ptr_array_find_with_equal_func1( + self->pending_responds, &user_data->sequence_id, + compare_pending_by_sequence_id, &result_index); + g_return_if_fail(found); + FlKeyboardPendingEvent* pending = FL_KEYBOARD_PENDING_EVENT( + g_ptr_array_index(self->pending_responds, result_index)); + g_return_if_fail(pending != nullptr); + fl_keyboard_pending_event_mark_replied(pending, handled); + // All responders have replied. + if (fl_keyboard_pending_event_is_complete(pending)) { + g_object_unref(user_data_ptr); + gpointer removed = + g_ptr_array_remove_index_fast(self->pending_responds, result_index); + g_return_if_fail(removed == pending); + bool should_redispatch = + !fl_keyboard_pending_event_get_any_handled(pending) && + !fl_keyboard_view_delegate_text_filter_key_press( + view_delegate, fl_keyboard_pending_event_get_event(pending)); + if (should_redispatch) { + g_ptr_array_add(self->pending_redispatches, pending); + fl_keyboard_view_delegate_redispatch_event( + view_delegate, + FL_KEY_EVENT(fl_keyboard_pending_event_get_event(pending))); + } else { + g_object_unref(pending); + } + } +} + +static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate, + guint keycode, + gint group, + gint level) { + GdkKeymapKey key = {keycode, group, level}; + constexpr int kBmpMax = 0xD7FF; + guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key); + return origin < kBmpMax ? origin : 0xFFFF; +} + +// Make sure that Flutter has derived the layout for the group of the event, +// if the event contains a goal keycode. +static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) { + g_autoptr(FlKeyboardViewDelegate) view_delegate = + FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); + if (view_delegate == nullptr) { + return; + } + + guint8 group = fl_key_event_get_group(event); + if (fl_keyboard_layout_has_group(self->derived_layout, group)) { + return; + } + if (self->keycode_to_goals->find(fl_key_event_get_keycode(event)) == + self->keycode_to_goals->end()) { + return; + } + + // Clone all mandatory goals. Each goal is removed from this cloned map when + // fulfilled, and the remaining ones will be assigned to a default position. + std::map remaining_mandatory_goals = + *self->logical_to_mandatory_goals; + +#ifdef DEBUG_PRINT_LAYOUT + std::string debug_layout_data; + for (uint16_t keycode = 0; keycode < 128; keycode += 1) { + std::vector this_key_clues = { + convert_key_to_char(view_delegate, keycode, group, 0), + convert_key_to_char(view_delegate, keycode, group, 1), // Shift + }; + debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0], + this_key_clues[1]); + } +#endif + + // It's important to only traverse layout goals instead of all keycodes. + // Some key codes outside of the standard keyboard also gives alpha-numeric + // letters, and will therefore take over mandatory goals from standard + // keyboard keys if they come first. Example: French keyboard digit 1. + for (const LayoutGoal& keycode_goal : layout_goals) { + uint16_t keycode = keycode_goal.keycode; + std::vector this_key_clues = { + convert_key_to_char(view_delegate, keycode, group, 0), + convert_key_to_char(view_delegate, keycode, group, 1), // Shift + }; + + // The logical key should be the first available clue from below: + // + // - Mandatory goal, if it matches any clue. This ensures that all alnum + // keys can be found somewhere. + // - US layout, if neither clue of the key is EASCII. This ensures that + // there are no non-latin logical keys. + // - A value derived on the fly from keycode & keyval. + for (uint16_t clue : this_key_clues) { + auto matching_goal = remaining_mandatory_goals.find(clue); + if (matching_goal != remaining_mandatory_goals.end()) { + // Found a key that produces a mandatory char. Use it. + g_return_if_fail(fl_keyboard_layout_get_logical_key( + self->derived_layout, group, keycode) == 0); + fl_keyboard_layout_set_logical_key(self->derived_layout, group, keycode, + clue); + remaining_mandatory_goals.erase(matching_goal); + break; + } + } + bool has_any_eascii = + is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]); + // See if any produced char meets the requirement as a logical key. + if (fl_keyboard_layout_get_logical_key(self->derived_layout, group, + keycode) == 0 && + !has_any_eascii) { + auto found_us_layout = self->keycode_to_goals->find(keycode); + if (found_us_layout != self->keycode_to_goals->end()) { + fl_keyboard_layout_set_logical_key( + self->derived_layout, group, keycode, + found_us_layout->second->logical_key); + } + } + } + + // Ensure all mandatory goals are assigned. + for (const auto mandatory_goal_iter : remaining_mandatory_goals) { + const LayoutGoal* goal = mandatory_goal_iter.second; + fl_keyboard_layout_set_logical_key(self->derived_layout, group, + goal->keycode, goal->logical_key); + } +} + +static void fl_keyboard_manager_dispose(GObject* object) { + FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object); + + g_weak_ref_clear(&self->view_delegate); + + self->keycode_to_goals.reset(); + self->logical_to_mandatory_goals.reset(); + + g_ptr_array_free(self->responder_list, TRUE); + g_ptr_array_set_free_func(self->pending_responds, g_object_unref); + g_ptr_array_free(self->pending_responds, TRUE); + g_ptr_array_free(self->pending_redispatches, TRUE); + g_clear_object(&self->derived_layout); + + G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object); +} + +static void fl_keyboard_manager_class_init(FlKeyboardManagerClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_dispose; +} + +static void fl_keyboard_manager_init(FlKeyboardManager* self) { + self->derived_layout = fl_keyboard_layout_new(); + + self->keycode_to_goals = + std::make_unique>(); + self->logical_to_mandatory_goals = + std::make_unique>(); + for (const LayoutGoal& goal : layout_goals) { + (*self->keycode_to_goals)[goal.keycode] = &goal; + if (goal.mandatory) { + (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal; + } + } + + self->responder_list = g_ptr_array_new_with_free_func(g_object_unref); + + self->pending_responds = g_ptr_array_new(); + self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref); + + self->last_sequence_id = 1; +} + +FlKeyboardManager* fl_keyboard_manager_new( + FlKeyboardViewDelegate* view_delegate) { + g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr); + + FlKeyboardManager* self = FL_KEYBOARD_MANAGER( + g_object_new(fl_keyboard_manager_get_type(), nullptr)); + + g_weak_ref_init(&self->view_delegate, view_delegate); + + // The embedder responder must be added before the channel responder. + g_ptr_array_add( + self->responder_list, + FL_KEY_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); + g_autoptr(FlKeyboardViewDelegate) view_delegate = + FL_KEYBOARD_VIEW_DELEGATE(g_weak_ref_get(&self->view_delegate)); + if (view_delegate == nullptr) { + return; + } + fl_keyboard_view_delegate_send_key_event( + view_delegate, event, callback, callback_user_data); + }, + self))); + g_ptr_array_add(self->responder_list, + FL_KEY_RESPONDER(fl_key_channel_responder_new( + fl_keyboard_view_delegate_get_messenger(view_delegate)))); + + return self; +} + +gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self, + FlKeyEvent* event) { + g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE); + g_return_val_if_fail(event != nullptr, FALSE); + + guarantee_layout(self, event); + + uint64_t incoming_hash = fl_key_event_hash(event); + if (fl_keyboard_manager_remove_redispatched(self, incoming_hash)) { + return FALSE; + } + + FlKeyboardPendingEvent* pending = fl_keyboard_pending_event_new( + event, ++self->last_sequence_id, self->responder_list->len); + + g_ptr_array_add(self->pending_responds, pending); + FlKeyboardManagerUserData* user_data = fl_keyboard_manager_user_data_new( + self, fl_keyboard_pending_event_get_sequence_id(pending)); + uint64_t specified_logical_key = fl_keyboard_layout_get_logical_key( + self->derived_layout, fl_key_event_get_group(event), + fl_key_event_get_keycode(event)); + for (guint i = 0; i < self->responder_list->len; i++) { + FlKeyResponder* responder = + FL_KEY_RESPONDER(g_ptr_array_index(self->responder_list, i)); + fl_key_responder_handle_event(responder, event, + responder_handle_event_callback, user_data, + specified_logical_key); + } + + return TRUE; +} + +gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) { + g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE); + return self->pending_responds->len == 0 && + self->pending_redispatches->len == 0; +} + +void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self, + guint state, + double event_time) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self)); + + // The embedder responder is the first element in + // FlKeyboardManager.responder_list. + FlKeyEmbedderResponder* responder = + FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0)); + fl_key_embedder_responder_sync_modifiers_if_needed(responder, state, + event_time); +} + +GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) { + g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr); + + // The embedder responder is the first element in + // FlKeyboardManager.responder_list. + FlKeyEmbedderResponder* responder = + FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0)); + return fl_key_embedder_responder_get_pressed_state(responder); +} + +void fl_keyboard_manager_notify_layout_changed(FlKeyboardManager* self) { + g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self)); + g_clear_object(&self->derived_layout); + self->derived_layout = fl_keyboard_layout_new(); +} diff --git a/shell/platform/linux/fl_keyboard_manager.h b/shell/platform/linux/fl_keyboard_manager.h new file mode 100644 index 0000000000000..be1be5b64d7a9 --- /dev/null +++ b/shell/platform/linux/fl_keyboard_manager.h @@ -0,0 +1,105 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_ +#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_ + +#include + +#include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h" + +G_BEGIN_DECLS + +#define FL_TYPE_KEYBOARD_MANAGER fl_keyboard_manager_get_type() +G_DECLARE_FINAL_TYPE(FlKeyboardManager, + fl_keyboard_manager, + FL, + KEYBOARD_MANAGER, + GObject); + +/** + * FlKeyboardManager: + * + * Processes keyboard events and cooperate with `TextInputManager`. + * + * A keyboard event goes through a few sections, each can choose to handle the + * event, and only unhandled events can move to the next section: + * + * - Keyboard: Dispatch to the embedder responder and the channel responder + * simultaneously. After both responders have responded (asynchronously), the + * event is considered handled if either responder handles it. + * - Text input: Events are sent to IM filter (usually owned by + * `TextInputManager`) and are handled synchronously. + * - Redispatching: Events are inserted back to the system for redispatching. + */ + +/** + * fl_keyboard_manager_new: + * @view_delegate: An interface that the manager requires to communicate with + * the platform. Usually implemented by FlView. + * + * Create a new #FlKeyboardManager. + * + * Returns: a new #FlKeyboardManager. + */ +FlKeyboardManager* fl_keyboard_manager_new( + FlKeyboardViewDelegate* view_delegate); + +/** + * fl_keyboard_manager_handle_event: + * @manager: the #FlKeyboardManager self. + * @event: the event to be dispatched. It is usually a wrap of a GdkEventKey. + * This event will be managed and released by #FlKeyboardManager. + * + * Make the manager process a system key event. This might eventually send + * messages to the framework, trigger text input effects, or redispatch the + * event back to the system. + */ +gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* manager, + FlKeyEvent* event); + +/** + * fl_keyboard_manager_is_state_clear: + * @manager: the #FlKeyboardManager self. + * + * A debug-only method that queries whether the manager's various states are + * cleared, i.e. no pending events for redispatching or for responding. + * + * Returns: true if the manager's various states are cleared. + */ +gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* manager); + +/** + * fl_keyboard_manager_sync_modifier_if_needed: + * @manager: the #FlKeyboardManager self. + * @state: the state of the modifiers mask. + * @event_time: the time attribute of the incoming GDK event. + * + * If needed, synthesize modifier keys up and down event by comparing their + * current pressing states with the given modifiers mask. + */ +void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager, + guint state, + double event_time); + +/** + * fl_keyboard_manager_get_pressed_state: + * @manager: the #FlKeyboardManager self. + * + * Returns the keyboard pressed state. The hash table contains one entry per + * pressed keys, mapping from the logical key to the physical key.* + */ +GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager); + +/** + * fl_keyboard_manager_notify_layout_changed: + * @manager: the #FlKeyboardManager self. + * + * Notify the manager the keyboard layout has changed. + */ +void fl_keyboard_manager_notify_layout_changed(FlKeyboardManager* manager); + +G_END_DECLS + +#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_ diff --git a/shell/platform/linux/fl_keyboard_manager_test.cc b/shell/platform/linux/fl_keyboard_manager_test.cc new file mode 100644 index 0000000000000..9011662347163 --- /dev/null +++ b/shell/platform/linux/fl_keyboard_manager_test.cc @@ -0,0 +1,1145 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/linux/fl_keyboard_manager.h" + +#include +#include + +#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/key_mapping.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "flutter/shell/platform/linux/testing/mock_text_input_handler.h" +#include "flutter/testing/testing.h" + +#include "gmock/gmock.h" +#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).type, CallRecord::kKeyCallEmbedder); \ + 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(call_records[0].type, CallRecord::kKeyCallEmbedder); \ + EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \ + EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL)); \ + EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR)); \ + EXPECT_EQ(call_records[0].event->synthesized, false); \ + call_records.clear() + +namespace { +using ::flutter::testing::keycodes::kLogicalAltLeft; +using ::flutter::testing::keycodes::kLogicalBracketLeft; +using ::flutter::testing::keycodes::kLogicalComma; +using ::flutter::testing::keycodes::kLogicalControlLeft; +using ::flutter::testing::keycodes::kLogicalDigit1; +using ::flutter::testing::keycodes::kLogicalKeyA; +using ::flutter::testing::keycodes::kLogicalKeyB; +using ::flutter::testing::keycodes::kLogicalKeyM; +using ::flutter::testing::keycodes::kLogicalKeyQ; +using ::flutter::testing::keycodes::kLogicalMetaLeft; +using ::flutter::testing::keycodes::kLogicalMinus; +using ::flutter::testing::keycodes::kLogicalParenthesisRight; +using ::flutter::testing::keycodes::kLogicalSemicolon; +using ::flutter::testing::keycodes::kLogicalShiftLeft; +using ::flutter::testing::keycodes::kLogicalUnderscore; + +using ::flutter::testing::keycodes::kPhysicalAltLeft; +using ::flutter::testing::keycodes::kPhysicalControlLeft; +using ::flutter::testing::keycodes::kPhysicalKeyA; +using ::flutter::testing::keycodes::kPhysicalKeyB; +using ::flutter::testing::keycodes::kPhysicalMetaLeft; +using ::flutter::testing::keycodes::kPhysicalShiftLeft; + +// Hardware key codes. +typedef std::function AsyncKeyCallback; +typedef std::function ChannelCallHandler; +typedef std::function + EmbedderCallHandler; +typedef std::function RedispatchHandler; + +// A type that can record all kinds of effects that the keyboard handler +// triggers. +// +// An instance of `CallRecord` might not have all the fields filled. +typedef struct { + enum { + kKeyCallEmbedder, + kKeyCallChannel, + } type; + + AsyncKeyCallback callback; + std::unique_ptr event; + std::unique_ptr event_character; +} CallRecord; + +// Clone a C-string. +// +// Must be deleted by delete[]. +char* cloneString(const char* source) { + if (source == nullptr) { + return nullptr; + } + size_t charLen = strlen(source); + char* target = new char[charLen + 1]; + strncpy(target, source, charLen + 1); + return target; +} + +constexpr guint16 kKeyCodeKeyA = 0x26u; +constexpr guint16 kKeyCodeKeyB = 0x38u; +constexpr guint16 kKeyCodeKeyM = 0x3au; +constexpr guint16 kKeyCodeDigit1 = 0x0au; +constexpr guint16 kKeyCodeMinus = 0x14u; +constexpr guint16 kKeyCodeSemicolon = 0x2fu; +constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u; + +static constexpr char kKeyEventChannelName[] = "flutter/keyevent"; + +// All key clues for a keyboard layout. +// +// The index is (keyCode * 2 + hasShift), where each value is the character for +// this key (GTK only supports UTF-16.) Since the maximum keycode of interest +// is 128, it has a total of 256 entries.. +typedef std::array MockGroupLayoutData; +typedef std::vector MockLayoutData; + +extern const MockLayoutData kLayoutUs; +extern const MockLayoutData kLayoutRussian; +extern const MockLayoutData kLayoutFrench; + +G_BEGIN_DECLS + +G_DECLARE_FINAL_TYPE(FlMockViewDelegate, + fl_mock_view_delegate, + FL, + MOCK_VIEW_DELEGATE, + GObject); + +G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger, + fl_mock_key_binary_messenger, + FL, + MOCK_KEY_BINARY_MESSENGER, + GObject) + +G_END_DECLS + +MATCHER_P(MethodSuccessResponse, result, "") { + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodResponse) response = + fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr); + fl_method_response_get_result(response, nullptr); + if (fl_value_equal(fl_method_response_get_result(response, nullptr), + result)) { + return true; + } + *result_listener << ::testing::PrintToString(response); + return false; +} + +/***** FlMockKeyBinaryMessenger *****/ +/* Mock a binary messenger that only processes messages from the embedding on + * the key event channel, and does so according to the callback set by + * fl_mock_key_binary_messenger_set_callback_handler */ + +struct _FlMockKeyBinaryMessenger { + GObject parent_instance; + + ChannelCallHandler callback_handler; +}; + +static void fl_mock_key_binary_messenger_iface_init( + FlBinaryMessengerInterface* iface); + +G_DEFINE_TYPE_WITH_CODE( + FlMockKeyBinaryMessenger, + fl_mock_key_binary_messenger, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(), + fl_mock_key_binary_messenger_iface_init)) + +static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) {} + +static void fl_mock_key_binary_messenger_dispose(GObject* object) { + G_OBJECT_CLASS(fl_mock_key_binary_messenger_parent_class)->dispose(object); +} + +static void fl_mock_key_binary_messenger_class_init( + FlMockKeyBinaryMessengerClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_mock_key_binary_messenger_dispose; +} + +static void fl_mock_key_binary_messenger_send_on_channel( + FlBinaryMessenger* messenger, + const gchar* channel, + GBytes* message, + GCancellable* cancellable, + GAsyncReadyCallback callback, + gpointer user_data) { + FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger); + + if (callback != nullptr) { + EXPECT_STREQ(channel, kKeyEventChannelName); + self->callback_handler([self, cancellable, callback, + user_data](bool handled) { + g_autoptr(GTask) task = + g_task_new(self, cancellable, callback, user_data); + g_autoptr(FlValue) result = fl_value_new_map(); + fl_value_set_string_take(result, "handled", fl_value_new_bool(handled)); + g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); + g_autoptr(GError) error = nullptr; + GBytes* data = fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), + result, &error); + + g_task_return_pointer(task, data, + reinterpret_cast(g_bytes_unref)); + }); + } +} + +static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish( + FlBinaryMessenger* messenger, + GAsyncResult* result, + GError** error) { + return static_cast(g_task_propagate_pointer(G_TASK(result), error)); +} + +static void fl_mock_binary_messenger_resize_channel( + FlBinaryMessenger* messenger, + const gchar* channel, + int64_t new_size) { + // Mock implementation. Do nothing. +} + +static void fl_mock_binary_messenger_set_warns_on_channel_overflow( + FlBinaryMessenger* messenger, + const gchar* channel, + bool warns) { + // Mock implementation. Do nothing. +} + +static void fl_mock_key_binary_messenger_iface_init( + FlBinaryMessengerInterface* iface) { + iface->set_message_handler_on_channel = + [](FlBinaryMessenger* messenger, const gchar* channel, + FlBinaryMessengerMessageHandler handler, gpointer user_data, + GDestroyNotify destroy_notify) { + EXPECT_STREQ(channel, kKeyEventChannelName); + // No need to mock. The key event channel expects no incoming messages + // from the framework. + }; + iface->send_response = [](FlBinaryMessenger* messenger, + FlBinaryMessengerResponseHandle* response_handle, + GBytes* response, GError** error) -> gboolean { + // The key event channel expects no incoming messages from the framework, + // hence no responses either. + g_return_val_if_reached(TRUE); + return TRUE; + }; + iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel; + iface->send_on_channel_finish = + fl_mock_key_binary_messenger_send_on_channel_finish; + iface->resize_channel = fl_mock_binary_messenger_resize_channel; + iface->set_warns_on_channel_overflow = + fl_mock_binary_messenger_set_warns_on_channel_overflow; +} + +static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() { + FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER( + g_object_new(fl_mock_key_binary_messenger_get_type(), NULL)); + + // Added to stop compiler complaining about an unused function. + FL_IS_MOCK_KEY_BINARY_MESSENGER(self); + + return self; +} + +static void fl_mock_key_binary_messenger_set_callback_handler( + FlMockKeyBinaryMessenger* self, + ChannelCallHandler handler) { + self->callback_handler = std::move(handler); +} + +/***** FlMockViewDelegate *****/ + +struct _FlMockViewDelegate { + GObject parent_instance; + + FlMockKeyBinaryMessenger* messenger; + EmbedderCallHandler embedder_handler; + bool text_filter_result; + RedispatchHandler redispatch_handler; + const MockLayoutData* layout_data; +}; + +static void fl_mock_view_keyboard_delegate_iface_init( + FlKeyboardViewDelegateInterface* iface); + +G_DEFINE_TYPE_WITH_CODE( + FlMockViewDelegate, + fl_mock_view_delegate, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(), + fl_mock_view_keyboard_delegate_iface_init)) + +static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {} + +static void fl_mock_view_delegate_dispose(GObject* object) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(object); + + g_clear_object(&self->messenger); + + G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object); +} + +static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) { + G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose; +} + +static void fl_mock_view_keyboard_send_key_event( + FlKeyboardViewDelegate* view_delegate, + const FlutterKeyEvent* event, + FlutterKeyEventCallback callback, + void* user_data) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); + self->embedder_handler(event, [callback, user_data](bool handled) { + if (callback != nullptr) { + callback(handled, user_data); + } + }); +} + +static gboolean fl_mock_view_keyboard_text_filter_key_press( + FlKeyboardViewDelegate* view_delegate, + FlKeyEvent* event) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); + return self->text_filter_result; +} + +static FlBinaryMessenger* fl_mock_view_keyboard_get_messenger( + FlKeyboardViewDelegate* view_delegate) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); + return FL_BINARY_MESSENGER(self->messenger); +} + +static void fl_mock_view_keyboard_redispatch_event( + FlKeyboardViewDelegate* view_delegate, + FlKeyEvent* event) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); + if (self->redispatch_handler) { + self->redispatch_handler(event); + } +} + +static guint fl_mock_view_keyboard_lookup_key( + FlKeyboardViewDelegate* view_delegate, + const GdkKeymapKey* key) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); + guint8 group = static_cast(key->group); + EXPECT_LT(group, self->layout_data->size()); + const MockGroupLayoutData* group_layout = (*self->layout_data)[group]; + EXPECT_TRUE(group_layout != nullptr); + EXPECT_TRUE(key->level == 0 || key->level == 1); + bool shift = key->level == 1; + return (*group_layout)[key->keycode * 2 + shift]; +} + +static void fl_mock_view_keyboard_delegate_iface_init( + FlKeyboardViewDelegateInterface* iface) { + iface->send_key_event = fl_mock_view_keyboard_send_key_event; + iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press; + iface->get_messenger = fl_mock_view_keyboard_get_messenger; + iface->redispatch_event = fl_mock_view_keyboard_redispatch_event; + iface->lookup_key = fl_mock_view_keyboard_lookup_key; +} + +static FlMockViewDelegate* fl_mock_view_delegate_new() { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE( + g_object_new(fl_mock_view_delegate_get_type(), nullptr)); + + // Added to stop compiler complaining about an unused function. + FL_IS_MOCK_VIEW_DELEGATE(self); + + self->messenger = fl_mock_key_binary_messenger_new(); + + return self; +} + +static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self, + EmbedderCallHandler handler) { + self->embedder_handler = std::move(handler); +} + +static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self, + bool result) { + self->text_filter_result = result; +} + +static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self, + RedispatchHandler handler) { + self->redispatch_handler = std::move(handler); +} + +static void fl_mock_view_set_layout(FlMockViewDelegate* self, + const MockLayoutData* layout) { + self->layout_data = layout; +} + +/***** End FlMockViewDelegate *****/ + +class KeyboardTester { + public: + KeyboardTester() { + view_ = fl_mock_view_delegate_new(); + respondToEmbedderCallsWith(false); + respondToChannelCallsWith(false); + respondToTextInputWith(false); + setLayout(kLayoutUs); + + handler_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_)); + } + + ~KeyboardTester() { + g_clear_object(&view_); + g_clear_object(&handler_); + g_clear_pointer(&redispatched_events_, g_ptr_array_unref); + } + + FlKeyboardManager* handler() { return handler_; } + + // Block until all GdkMainLoop messages are processed, which is basically + // used only for channel messages. + void flushChannelMessages() { + GMainLoop* loop = g_main_loop_new(nullptr, 0); + g_idle_add(_flushChannelMessagesCb, loop); + g_main_loop_run(loop); + } + + // Dispatch each of the given events, expect their results to be false + // (unhandled), and clear the event array. + // + // Returns the number of events redispatched. If any result is unexpected + // (handled), return a minus number `-x` instead, where `x` is the index of + // the first unexpected redispatch. + int redispatchEventsAndClear(GPtrArray* events) { + guint event_count = events->len; + int first_error = -1; + during_redispatch_ = true; + for (guint event_id = 0; event_id < event_count; event_id += 1) { + FlKeyEvent* event = FL_KEY_EVENT(g_ptr_array_index(events, event_id)); + bool handled = fl_keyboard_manager_handle_event(handler_, event); + EXPECT_FALSE(handled); + if (handled) { + first_error = first_error == -1 ? event_id : first_error; + } + } + during_redispatch_ = false; + g_ptr_array_set_size(events, 0); + return first_error < 0 ? event_count : -first_error; + } + + void respondToEmbedderCallsWith(bool response) { + fl_mock_view_set_embedder_handler( + view_, [response, this](const FlutterKeyEvent* event, + const AsyncKeyCallback& callback) { + EXPECT_FALSE(during_redispatch_); + callback(response); + }); + } + + void recordEmbedderCallsTo(std::vector& storage) { + fl_mock_view_set_embedder_handler( + view_, [&storage, this](const FlutterKeyEvent* event, + AsyncKeyCallback callback) { + EXPECT_FALSE(during_redispatch_); + auto new_event = std::make_unique(*event); + char* new_event_character = cloneString(event->character); + new_event->character = new_event_character; + storage.push_back(CallRecord{ + .type = CallRecord::kKeyCallEmbedder, + .callback = std::move(callback), + .event = std::move(new_event), + .event_character = std::unique_ptr(new_event_character), + }); + }); + } + + void respondToEmbedderCallsWithAndRecordsTo( + bool response, + std::vector& storage) { + fl_mock_view_set_embedder_handler( + view_, [&storage, response, this](const FlutterKeyEvent* event, + const AsyncKeyCallback& callback) { + EXPECT_FALSE(during_redispatch_); + auto new_event = std::make_unique(*event); + char* new_event_character = cloneString(event->character); + new_event->character = new_event_character; + storage.push_back(CallRecord{ + .type = CallRecord::kKeyCallEmbedder, + .event = std::move(new_event), + .event_character = std::unique_ptr(new_event_character), + }); + callback(response); + }); + } + + void respondToChannelCallsWith(bool response) { + fl_mock_key_binary_messenger_set_callback_handler( + view_->messenger, [response, this](const AsyncKeyCallback& callback) { + EXPECT_FALSE(during_redispatch_); + callback(response); + }); + } + + void recordChannelCallsTo(std::vector& storage) { + fl_mock_key_binary_messenger_set_callback_handler( + view_->messenger, [&storage, this](AsyncKeyCallback callback) { + EXPECT_FALSE(during_redispatch_); + storage.push_back(CallRecord{ + .type = CallRecord::kKeyCallChannel, + .callback = std::move(callback), + }); + }); + } + + void respondToTextInputWith(bool response) { + fl_mock_view_set_text_filter_result(view_, response); + } + + void recordRedispatchedEventsTo(GPtrArray* storage) { + redispatched_events_ = g_ptr_array_ref(storage); + fl_mock_view_set_redispatch_handler(view_, [this](FlKeyEvent* key) { + g_ptr_array_add(redispatched_events_, g_object_ref(key)); + }); + } + + void setLayout(const MockLayoutData& layout) { + fl_mock_view_set_layout(view_, &layout); + if (handler_ != nullptr) { + fl_keyboard_manager_notify_layout_changed(handler_); + } + } + + private: + FlMockViewDelegate* view_; + FlKeyboardManager* handler_ = nullptr; + GPtrArray* redispatched_events_ = nullptr; + bool during_redispatch_ = false; + + static gboolean _flushChannelMessagesCb(gpointer data) { + g_autoptr(GMainLoop) loop = reinterpret_cast(data); + g_main_loop_quit(loop); + return FALSE; + } +}; + +// Make sure that the keyboard can be disposed without crashes when there are +// unresolved pending events. +TEST(FlKeyboardManagerTest, DisposeWithUnresolvedPends) { + KeyboardTester tester; + std::vector call_records; + + // Record calls so that they aren't responded. + tester.recordEmbedderCallsTo(call_records); + g_autoptr(FlKeyEvent) event1 = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + fl_keyboard_manager_handle_event(tester.handler(), event1); + + tester.respondToEmbedderCallsWith(true); + g_autoptr(FlKeyEvent) event2 = fl_key_event_new( + 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + fl_keyboard_manager_handle_event(tester.handler(), event2); + + tester.flushChannelMessages(); + + // Passes if the cleanup does not crash. +} + +TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) { + KeyboardTester tester; + std::vector call_records; + g_autoptr(GPtrArray) redispatched = + g_ptr_array_new_with_free_func(g_object_unref); + + gboolean handler_handled = false; + + /// Test 1: One event that is handled by the framework + tester.recordEmbedderCallsTo(call_records); + tester.recordRedispatchedEventsTo(redispatched); + + // Dispatch a key event + g_autoptr(FlKeyEvent) event1 = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_EQ(call_records.size(), 1u); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "a", false); + + call_records[0].callback(true); + tester.flushChannelMessages(); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); + call_records.clear(); + + /// Test 2: Two events that are unhandled by the framework + g_autoptr(FlKeyEvent) event2 = fl_key_event_new( + 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_EQ(call_records.size(), 1u); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, + kLogicalKeyA, nullptr, false); + + // Dispatch another key event + g_autoptr(FlKeyEvent) event3 = fl_key_event_new( + 0, TRUE, kKeyCodeKeyB, GDK_KEY_b, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event3); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_EQ(call_records.size(), 2u); + EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB, + kLogicalKeyB, "b", false); + + // Resolve the second event first to test out-of-order response + call_records[1].callback(false); + EXPECT_EQ(redispatched->len, 1u); + EXPECT_EQ( + fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 0))), + 0x62u); + call_records[0].callback(false); + tester.flushChannelMessages(); + EXPECT_EQ(redispatched->len, 2u); + EXPECT_EQ( + fl_key_event_get_keyval(FL_KEY_EVENT(g_ptr_array_index(redispatched, 1))), + 0x61u); + + EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.handler())); + call_records.clear(); + + // Resolve redispatches + EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2); + tester.flushChannelMessages(); + EXPECT_EQ(call_records.size(), 0u); + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); + + /// Test 3: Dispatch the same event again to ensure that prevention from + /// redispatching only works once. + g_autoptr(FlKeyEvent) event4 = fl_key_event_new( + 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event4); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_EQ(call_records.size(), 1u); + + call_records[0].callback(true); + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); +} + +TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) { + KeyboardTester tester; + gboolean handler_handled = false; + std::vector call_records; + g_autoptr(GPtrArray) redispatched = + g_ptr_array_new_with_free_func(g_object_unref); + + /// Test 1: One event that is handled by the framework + tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records); + tester.recordRedispatchedEventsTo(redispatched); + + // Dispatch a key event + g_autoptr(FlKeyEvent) event1 = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(call_records.size(), 1u); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "a", false); + EXPECT_EQ(redispatched->len, 0u); + call_records.clear(); + + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); + g_ptr_array_set_size(redispatched, 0); + + /// Test 2: An event unhandled by the framework + tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records); + g_autoptr(FlKeyEvent) event2 = fl_key_event_new( + 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(call_records.size(), 1u); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, + kLogicalKeyA, nullptr, false); + EXPECT_EQ(redispatched->len, 1u); + call_records.clear(); + + EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.handler())); + + EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); + EXPECT_EQ(call_records.size(), 0u); + + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); +} + +TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) { + KeyboardTester tester; + std::vector call_records; + g_autoptr(GPtrArray) redispatched = + g_ptr_array_new_with_free_func(g_object_unref); + + gboolean handler_handled = false; + + tester.recordEmbedderCallsTo(call_records); + tester.recordChannelCallsTo(call_records); + tester.recordRedispatchedEventsTo(redispatched); + + /// Test 1: One delegate responds true, the other false + + g_autoptr(FlKeyEvent) event1 = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event1); + + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_EQ(call_records.size(), 2u); + + EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); + EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); + + call_records[0].callback(true); + call_records[1].callback(false); + tester.flushChannelMessages(); + EXPECT_EQ(redispatched->len, 0u); + + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); + call_records.clear(); + + /// Test 2: All delegates respond false + g_autoptr(FlKeyEvent) event2 = fl_key_event_new( + 0, FALSE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event2); + + EXPECT_EQ(handler_handled, true); + EXPECT_EQ(redispatched->len, 0u); + EXPECT_EQ(call_records.size(), 2u); + + EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); + EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); + + call_records[0].callback(false); + call_records[1].callback(false); + + call_records.clear(); + + // Resolve redispatch + tester.flushChannelMessages(); + EXPECT_EQ(redispatched->len, 1u); + EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); + EXPECT_EQ(call_records.size(), 0u); + + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); +} + +TEST(FlKeyboardManagerTest, TextInputHandlerReturnsFalse) { + KeyboardTester tester; + g_autoptr(GPtrArray) redispatched = + g_ptr_array_new_with_free_func(g_object_unref); + gboolean handler_handled = false; + tester.recordRedispatchedEventsTo(redispatched); + tester.respondToTextInputWith(false); + + // Dispatch a key event. + g_autoptr(FlKeyEvent) event = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + // The event was redispatched because no one handles it. + EXPECT_EQ(redispatched->len, 1u); + + // Resolve redispatched event. + EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); + + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); +} + +TEST(FlKeyboardManagerTest, TextInputHandlerReturnsTrue) { + KeyboardTester tester; + g_autoptr(GPtrArray) redispatched = + g_ptr_array_new_with_free_func(g_object_unref); + gboolean handler_handled = false; + tester.recordRedispatchedEventsTo(redispatched); + tester.respondToTextInputWith(true); + + // Dispatch a key event. + g_autoptr(FlKeyEvent) event = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + handler_handled = fl_keyboard_manager_handle_event(tester.handler(), event); + tester.flushChannelMessages(); + EXPECT_EQ(handler_handled, true); + // The event was not redispatched because handler handles it. + EXPECT_EQ(redispatched->len, 0u); + + EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.handler())); +} + +TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) { + KeyboardTester tester; + + std::vector call_records; + tester.recordEmbedderCallsTo(call_records); + + auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) { + g_autoptr(FlKeyEvent) event1 = fl_key_event_new( + 0, TRUE, keycode, keyval, static_cast(0), group); + fl_keyboard_manager_handle_event(tester.handler(), event1); + g_autoptr(FlKeyEvent) event2 = fl_key_event_new( + 0, FALSE, keycode, keyval, static_cast(0), group); + fl_keyboard_manager_handle_event(tester.handler(), event2); + }; + + /* US keyboard layout */ + + sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA + VERIFY_DOWN(kLogicalKeyA, "a"); + + sendTap(kKeyCodeKeyA, GDK_KEY_A, 0); // Shift-KeyA + VERIFY_DOWN(kLogicalKeyA, "A"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0); // Shift-Digit1 + VERIFY_DOWN(kLogicalDigit1, "!"); + + sendTap(kKeyCodeMinus, GDK_KEY_minus, 0); // Minus + VERIFY_DOWN(kLogicalMinus, "-"); + + sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0); // Shift-Minus + VERIFY_DOWN(kLogicalUnderscore, "_"); + + /* French keyboard layout, group 3, which is when the input method is showing + * "Fr" */ + + tester.setLayout(kLayoutFrench); + + sendTap(kKeyCodeKeyA, GDK_KEY_q, 3); // KeyA + VERIFY_DOWN(kLogicalKeyQ, "q"); + + sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3); // Shift-KeyA + VERIFY_DOWN(kLogicalKeyQ, "Q"); + + sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3); // ; but prints M + VERIFY_DOWN(kLogicalKeyM, "m"); + + sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3); // M but prints , + VERIFY_DOWN(kLogicalComma, ","); + + sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3); // Digit1 + VERIFY_DOWN(kLogicalDigit1, "&"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 3); // Shift-Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3); // Minus + VERIFY_DOWN(kLogicalParenthesisRight, ")"); + + sendTap(kKeyCodeMinus, GDK_KEY_degree, 3); // Shift-Minus + VERIFY_DOWN(static_cast(L'°'), "°"); + + /* French keyboard layout, group 0, which is pressing the "extra key for + * triggering input method" key once after switching to French IME. */ + + sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA + VERIFY_DOWN(kLogicalKeyA, "a"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + /* Russian keyboard layout, group 2 */ + tester.setLayout(kLayoutRussian); + + sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2); // KeyA + VERIFY_DOWN(kLogicalKeyA, "ф"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 2); // Shift-Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2); + VERIFY_DOWN(kLogicalBracketLeft, "х"); + + /* Russian keyboard layout, group 0 */ + sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA + VERIFY_DOWN(kLogicalKeyA, "a"); + + sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0); + VERIFY_DOWN(kLogicalBracketLeft, "["); +} + +TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) { + KeyboardTester tester; + std::vector call_records; + tester.recordEmbedderCallsTo(call_records); + + auto verifyModifierIsSynthesized = [&](GdkModifierType mask, + uint64_t physical, uint64_t logical) { + // Modifier is pressed. + guint state = mask; + fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1000); + EXPECT_EQ(call_records.size(), 1u); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical, + logical, NULL, true); + // Modifier is released. + state = state ^ mask; + fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1001); + EXPECT_EQ(call_records.size(), 2u); + EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical, + NULL, true); + call_records.clear(); + }; + + // No modifiers pressed. + guint state = 0; + fl_keyboard_manager_sync_modifier_if_needed(tester.handler(), state, 1000); + EXPECT_EQ(call_records.size(), 0u); + call_records.clear(); + + // Press and release each modifier once. + verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft, + kLogicalControlLeft); + verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft, + kLogicalMetaLeft); + verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft); + verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft, + kLogicalShiftLeft); +} + +TEST(FlKeyboardManagerTest, GetPressedState) { + KeyboardTester tester; + tester.respondToTextInputWith(true); + + // Dispatch a key event. + g_autoptr(FlKeyEvent) event = fl_key_event_new( + 0, TRUE, kKeyCodeKeyA, GDK_KEY_a, static_cast(0), 0); + fl_keyboard_manager_handle_event(tester.handler(), event); + + GHashTable* pressedState = + fl_keyboard_manager_get_pressed_state(tester.handler()); + EXPECT_EQ(g_hash_table_size(pressedState), 1u); + + gpointer physical_key = + g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA)); + EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA); +} + +// The following layout data is generated using DEBUG_PRINT_LAYOUT. + +const MockGroupLayoutData kLayoutUs0{{ + // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 + 0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 + 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c + 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 + 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 + 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 + 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c + 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 + 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 + 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 + 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c + 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 + 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 + 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 + 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c + 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 + 0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x64 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 + 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c +}}; + +const MockGroupLayoutData kLayoutRussian0{ + // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 + 0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 + 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c + 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 + 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 + 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 + 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c + 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 + 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 + 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 + 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c + 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 + 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 + 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 + 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c + 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 + 0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c + 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 + 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 + 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c +}; + +const MockGroupLayoutData kLayoutRussian2{{ + // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 + 0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022, // 0x08 + 0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a, // 0x0c + 0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 + 0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000, // 0x14 + 0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb, // 0x18 + 0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb, // 0x1c + 0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff, // 0x20 + 0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9, // 0x24 + 0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2, // 0x28 + 0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6, // 0x2c + 0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f, // 0x30 + 0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed, // 0x34 + 0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2, // 0x38 + 0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 + 0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff, // 0x5c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x64 + 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000, // 0x68 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, // 0x78 + 0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x7c +}}; + +const MockGroupLayoutData kLayoutFrench0 = { + // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 + 0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 + 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c + 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 + 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 + 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 + 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c + 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 + 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 + 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 + 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c + 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 + 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 + 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 + 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c + 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 + 0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c + 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 + 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 + 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c +}; + +const MockGroupLayoutData kLayoutFrench3 = { + // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 + 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 + 0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032, // 0x08 + 0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036, // 0x0c + 0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030, // 0x10 + 0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041, // 0x14 + 0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 + 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c + 0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3, // 0x20 + 0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053, // 0x24 + 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 + 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d, // 0x2c + 0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5, // 0x30 + 0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 + 0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e, // 0x38 + 0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 + 0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c + 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 + 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 + 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, // 0x78 + 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x7c +}; + +const MockLayoutData kLayoutUs{&kLayoutUs0}; +const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr, + &kLayoutRussian2}; +const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr, + &kLayoutFrench3}; + +} // namespace diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 6e44ecbfd4cbb..1a342f9d3d74c 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -17,6 +17,7 @@ #include "flutter/shell/platform/linux/fl_framebuffer.h" #include "flutter/shell/platform/linux/fl_key_event.h" #include "flutter/shell/platform/linux/fl_keyboard_handler.h" +#include "flutter/shell/platform/linux/fl_keyboard_manager.h" #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h" #include "flutter/shell/platform/linux/fl_mouse_cursor_handler.h" #include "flutter/shell/platform/linux/fl_platform_handler.h" @@ -63,6 +64,8 @@ struct _FlView { FlScrollingManager* scrolling_manager; + FlKeyboardManager* keyboard_manager; + // Flutter system channel handlers. FlKeyboardHandler* keyboard_handler; FlTextInputHandler* text_input_handler; @@ -151,6 +154,9 @@ static void init_keyboard(FlView* self) { g_clear_object(&self->keyboard_handler); self->keyboard_handler = fl_keyboard_handler_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self)); + g_clear_object(&self->keyboard_manager); + self->keyboard_manager = + fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self)); } static void init_scrolling(FlView* self) { @@ -230,7 +236,7 @@ static gboolean send_pointer_button_event(FlView* self, GdkEvent* event) { gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); fl_scrolling_manager_set_last_mouse_position( self->scrolling_manager, event_x * scale_factor, event_y * scale_factor); - fl_keyboard_handler_sync_modifier_if_needed(self->keyboard_handler, + fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager, event_state, event_time); fl_engine_send_mouse_pointer_event( self->engine, self->view_id, phase, @@ -500,7 +506,7 @@ static gboolean motion_notify_event_cb(FlView* self, gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self)); - fl_keyboard_handler_sync_modifier_if_needed(self->keyboard_handler, + fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager, event_state, event_time); fl_engine_send_mouse_pointer_event( self->engine, self->view_id, self->button_state != 0 ? kMove : kHover, @@ -559,7 +565,7 @@ static gboolean leave_notify_event_cb(FlView* self, } static void keymap_keys_changed_cb(FlView* self) { - fl_keyboard_handler_notify_layout_changed(self->keyboard_handler); + fl_keyboard_manager_notify_layout_changed(self->keyboard_manager); } static void gesture_rotation_begin_cb(FlView* self) { @@ -728,11 +734,12 @@ static void fl_view_dispose(GObject* object) { g_clear_pointer(&self->background_color, gdk_rgba_free); g_clear_object(&self->window_state_monitor); g_clear_object(&self->scrolling_manager); - g_clear_object(&self->keyboard_handler); + g_clear_object(&self->keyboard_manager); if (self->keymap_keys_changed_cb_id != 0) { g_signal_handler_disconnect(self->keymap, self->keymap_keys_changed_cb_id); self->keymap_keys_changed_cb_id = 0; } + g_clear_object(&self->keyboard_handler); g_clear_object(&self->mouse_cursor_handler); g_clear_object(&self->platform_handler); g_clear_object(&self->view_accessible); @@ -755,8 +762,8 @@ static void fl_view_realize(GtkWidget* widget) { static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); - return fl_keyboard_handler_handle_event( - self->keyboard_handler, fl_key_event_new_from_gdk_event(gdk_event_copy( + return fl_keyboard_manager_handle_event( + self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy( reinterpret_cast(event)))); } @@ -764,8 +771,8 @@ static gboolean fl_view_key_press_event(GtkWidget* widget, GdkEventKey* event) { static gboolean fl_view_key_release_event(GtkWidget* widget, GdkEventKey* event) { FlView* self = FL_VIEW(widget); - return fl_keyboard_handler_handle_event( - self->keyboard_handler, fl_key_event_new_from_gdk_event(gdk_event_copy( + return fl_keyboard_manager_handle_event( + self->keyboard_manager, fl_key_event_new_from_gdk_event(gdk_event_copy( reinterpret_cast(event)))); } @@ -911,5 +918,5 @@ G_MODULE_EXPORT void fl_view_set_background_color(FlView* self, GHashTable* fl_view_get_keyboard_state(FlView* self) { g_return_val_if_fail(FL_IS_VIEW(self), nullptr); - return fl_keyboard_handler_get_pressed_state(self->keyboard_handler); + return fl_keyboard_manager_get_pressed_state(self->keyboard_manager); }