Split FlKeyboardManager out of FlKeyboardHandler (#55892)
They continue to share a FlKeyboardViewDelegate, but this will be split
too in a later change as part of moving these classes from FlView to
FlEngine.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index fd9a96d..283a93a 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -44799,6 +44799,9 @@
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_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 22c192e..a28e375 100644
--- a/shell/platform/linux/BUILD.gn
+++ b/shell/platform/linux/BUILD.gn
@@ -81,6 +81,7 @@
"fl_dart_project_private.h",
"fl_engine_private.h",
"fl_keyboard_handler.h",
+ "fl_keyboard_manager.h",
"fl_keyboard_pending_event.h",
"fl_keyboard_view_delegate.h",
"fl_key_event.h",
@@ -118,6 +119,7 @@
"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 @@
"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 e914925..266b9b1 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 <array>
-#include <cinttypes>
-#include <memory>
-#include <string>
-
-#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<std::map<uint16_t, const LayoutGoal*>> 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<std::map<uint64_t, const LayoutGoal*>>
- 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<gpointer>(a));
- uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
- return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
-}
-
-// Compare a #FlKeyboardPendingEvent with the given hash.
-static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
- FlKeyboardPendingEvent* pending =
- FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
- uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
- return fl_keyboard_pending_event_get_hash(pending) == hash;
-}
-
-// Try to remove a pending event from `pending_redispatches` with the target
-// 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<const uint64_t*>(&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<uint64_t, const LayoutGoal*> 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<uint16_t> 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<uint16_t> 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 @@
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 @@
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<std::map<uint16_t, const LayoutGoal*>>();
- self->logical_to_mandatory_goals =
- std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
- for (const LayoutGoal& goal : layout_goals) {
- (*self->keycode_to_goals)[goal.keycode] = &goal;
- if (goal.mandatory) {
- (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
- }
- }
-
- self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
-
- self->pending_responds = g_ptr_array_new();
- self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
-
- self->last_sequence_id = 1;
-}
+static void fl_keyboard_handler_init(FlKeyboardHandler* self) {}
FlKeyboardHandler* fl_keyboard_handler_new(
FlBinaryMessenger* messenger,
@@ -472,27 +91,6 @@
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 @@
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 0d551d6..7237755 100644
--- a/shell/platform/linux/fl_keyboard_handler.h
+++ b/shell/platform/linux/fl_keyboard_handler.h
@@ -21,17 +21,7 @@
/**
* 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 @@
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 f0e9815..a4d9278 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 <cstring>
-#include <vector>
-
-#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<void(bool handled)> AsyncKeyCallback;
-typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler;
-typedef std::function<void(const FlutterKeyEvent* event,
- AsyncKeyCallback callback)>
- EmbedderCallHandler;
-typedef std::function<void(FlKeyEvent*)> 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<FlutterKeyEvent> event;
- std::unique_ptr<char[]> 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<uint32_t, 256> MockGroupLayoutData;
-typedef std::vector<const MockGroupLayoutData*> 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 @@
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 {
+struct _FlMockKeyboardHandlerDelegate {
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<FlMockKeyBinaryMessengerPrivate*>( \
- 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<GDestroyNotify>(g_bytes_unref));
- });
- }
-}
-
-static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish(
- FlBinaryMessenger* messenger,
- GAsyncResult* result,
- GError** error) {
- return static_cast<GBytes*>(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 {
- 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))
+ G_IMPLEMENT_INTERFACE(
+ fl_keyboard_view_delegate_get_type(),
+ fl_mock_keyboard_handler_delegate_keyboard_view_delegate_iface_init))
-#define FL_MOCK_VIEW_DELEGATE_GET_PRIVATE(obj) \
- static_cast<FlMockViewDelegatePrivate*>( \
- fl_mock_view_delegate_get_instance_private(FL_MOCK_VIEW_DELEGATE(obj)))
+static void fl_mock_keyboard_handler_delegate_init(
+ FlMockKeyboardHandlerDelegate* self) {}
-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;
-}
-
-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_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<guint8>(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 @@
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<flutter::testing::MockBinaryMessenger> 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<CallRecord>& 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<FlutterKeyEvent>(*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<char[]>(new_event_character),
- });
- });
- }
-
- void respondToEmbedderCallsWithAndRecordsTo(
- bool response,
- std::vector<CallRecord>& 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<FlutterKeyEvent>(*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<char[]>(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<CallRecord>& 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<GMainLoop*>(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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(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<CallRecord> 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<GdkModifierType>(0), 0);
- handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event1);
- tester.flushChannelMessages();
- EXPECT_EQ(handler_handled, true);
- EXPECT_EQ(call_records.size(), 1u);
- EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
- kLogicalKeyA, "a", false);
- EXPECT_EQ(redispatched->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<GdkModifierType>(0), 0);
- handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event2);
- tester.flushChannelMessages();
- EXPECT_EQ(handler_handled, true);
- EXPECT_EQ(call_records.size(), 1u);
- EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
- kLogicalKeyA, nullptr, false);
- EXPECT_EQ(redispatched->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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(0), 0);
- handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
- tester.flushChannelMessages();
- EXPECT_EQ(handler_handled, true);
- // The event was redispatched because no one handles it.
- EXPECT_EQ(redispatched->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<GdkModifierType>(0), 0);
- handler_handled = fl_keyboard_handler_handle_event(tester.handler(), event);
- tester.flushChannelMessages();
- EXPECT_EQ(handler_handled, true);
- // The event was not redispatched because handler handles it.
- EXPECT_EQ(redispatched->len, 0u);
-
- EXPECT_TRUE(fl_keyboard_handler_is_state_clear(tester.handler()));
-}
-
-TEST(FlKeyboardHandlerTest, CorrectLogicalKeyForLayouts) {
- KeyboardTester tester;
-
- std::vector<CallRecord> 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<GdkModifierType>(0), group);
- fl_keyboard_handler_handle_event(tester.handler(), event1);
- g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
- 0, FALSE, keycode, keyval, static_cast<GdkModifierType>(0), group);
- fl_keyboard_handler_handle_event(tester.handler(), event2);
- };
-
- /* US keyboard layout */
-
- 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<uint32_t>(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<CallRecord> 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<GdkModifierType>(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<flutter::testing::MockBinaryMessenger> 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 @@
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 0000000..869d23b
--- /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 <array>
+#include <cinttypes>
+#include <memory>
+#include <string>
+
+#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<std::map<uint16_t, const LayoutGoal*>> 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<std::map<uint64_t, const LayoutGoal*>>
+ 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<gpointer>(a));
+ uint64_t sequence_id = *reinterpret_cast<const uint64_t*>(b);
+ return fl_keyboard_pending_event_get_sequence_id(pending) == sequence_id;
+}
+
+// Compare a #FlKeyboardPendingEvent with the given hash.
+static gboolean compare_pending_by_hash(gconstpointer a, gconstpointer b) {
+ FlKeyboardPendingEvent* pending =
+ FL_KEYBOARD_PENDING_EVENT(const_cast<gpointer>(a));
+ uint64_t hash = *reinterpret_cast<const uint64_t*>(b);
+ return fl_keyboard_pending_event_get_hash(pending) == hash;
+}
+
+// Try to remove a pending event from `pending_redispatches` with the target
+// 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<const uint64_t*>(&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<uint64_t, const LayoutGoal*> 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<uint16_t> 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<uint16_t> 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<std::map<uint16_t, const LayoutGoal*>>();
+ self->logical_to_mandatory_goals =
+ std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
+ for (const LayoutGoal& goal : layout_goals) {
+ (*self->keycode_to_goals)[goal.keycode] = &goal;
+ if (goal.mandatory) {
+ (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
+ }
+ }
+
+ self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
+
+ self->pending_responds = g_ptr_array_new();
+ self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
+
+ self->last_sequence_id = 1;
+}
+
+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 0000000..be1be5b
--- /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 <gdk/gdk.h>
+
+#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 0000000..9011662
--- /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 <cstring>
+#include <vector>
+
+#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<void(bool handled)> AsyncKeyCallback;
+typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler;
+typedef std::function<void(const FlutterKeyEvent* event,
+ AsyncKeyCallback callback)>
+ EmbedderCallHandler;
+typedef std::function<void(FlKeyEvent*)> 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<FlutterKeyEvent> event;
+ std::unique_ptr<char[]> 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<uint32_t, 256> MockGroupLayoutData;
+typedef std::vector<const MockGroupLayoutData*> 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<GDestroyNotify>(g_bytes_unref));
+ });
+ }
+}
+
+static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish(
+ FlBinaryMessenger* messenger,
+ GAsyncResult* result,
+ GError** error) {
+ return static_cast<GBytes*>(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<guint8>(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<CallRecord>& 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<FlutterKeyEvent>(*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<char[]>(new_event_character),
+ });
+ });
+ }
+
+ void respondToEmbedderCallsWithAndRecordsTo(
+ bool response,
+ std::vector<CallRecord>& 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<FlutterKeyEvent>(*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<char[]>(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<CallRecord>& 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<GMainLoop*>(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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<CallRecord> 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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(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<GdkModifierType>(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<CallRecord> 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<GdkModifierType>(0), group);
+ fl_keyboard_manager_handle_event(tester.handler(), event1);
+ g_autoptr(FlKeyEvent) event2 = fl_key_event_new(
+ 0, FALSE, keycode, keyval, static_cast<GdkModifierType>(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<uint32_t>(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<CallRecord> 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<GdkModifierType>(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 6e44ecb..1a342f9 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 @@
FlScrollingManager* scrolling_manager;
+ FlKeyboardManager* keyboard_manager;
+
// Flutter system channel handlers.
FlKeyboardHandler* keyboard_handler;
FlTextInputHandler* text_input_handler;
@@ -151,6 +154,9 @@
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 @@
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 @@
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 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 @@
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 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<GdkEvent*>(event))));
}
@@ -764,8 +771,8 @@
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<GdkEvent*>(event))));
}
@@ -911,5 +918,5 @@
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);
}