From d5820083a5dbf88df441bd1cb9a953ecb1fd803f Mon Sep 17 00:00:00 2001 From: Tong Mu Date: Thu, 21 Apr 2022 00:48:22 -0700 Subject: [PATCH] [Linux, Keyboard] Derive keyboard layout using printable information from system (flutter/engine#32665) * Workable layout deduction * Compilable test * Tests * Remove print, format * Use new way to pass logical key * Use unique_ptr * Format * Fix matrix format * Format * Cleanup * Remove duplicate * Remove printf * Update shell/platform/linux/fl_keyboard_manager.cc Co-authored-by: Greg Spencer Co-authored-by: Greg Spencer --- .../Source/FlutterKeyboardManager.mm | 2 +- .../platform/embedder/test_utils/key_codes.h | 1 + .../linux/fl_key_channel_responder.cc | 8 + .../linux/fl_key_channel_responder_test.cc | 27 ++ .../linux/fl_key_embedder_responder.cc | 11 +- .../linux/fl_key_embedder_responder_test.cc | 37 +- .../shell/platform/linux/fl_key_event.cc | 1 + .../shell/platform/linux/fl_key_event.h | 2 + .../shell/platform/linux/fl_key_responder.cc | 7 +- .../shell/platform/linux/fl_key_responder.h | 4 +- .../platform/linux/fl_keyboard_manager.cc | 294 +++++++++++-- .../platform/linux/fl_keyboard_manager.h | 1 - .../linux/fl_keyboard_manager_test.cc | 386 +++++++++++++++++- .../linux/fl_keyboard_view_delegate.cc | 16 + .../linux/fl_keyboard_view_delegate.h | 16 + .../flutter/shell/platform/linux/fl_view.cc | 34 ++ .../shell/platform/linux/key_mapping.cc | 52 +++ .../shell/platform/linux/key_mapping.h | 15 + 18 files changed, 848 insertions(+), 66 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm index 84e949424aa..90ebdf53628 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterKeyboardManager.mm @@ -283,7 +283,7 @@ typedef _Nullable _NSResponderPtr (^NextResponderProvider)(); // // - 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 + // - US layout, if neither clue of the key is EASCII. This ensures that // there are no non-latin logical keys. // - Derived on the fly from keyCode & characters. for (const LayoutClue& clue : thisKeyClues) { diff --git a/engine/src/flutter/shell/platform/embedder/test_utils/key_codes.h b/engine/src/flutter/shell/platform/embedder/test_utils/key_codes.h index 45f1f913bec..1694781d5fc 100644 --- a/engine/src/flutter/shell/platform/embedder/test_utils/key_codes.h +++ b/engine/src/flutter/shell/platform/embedder/test_utils/key_codes.h @@ -35,6 +35,7 @@ constexpr uint64_t kPhysicalSuspend = 0x00000014; constexpr uint64_t kPhysicalResume = 0x00000015; constexpr uint64_t kPhysicalTurbo = 0x00000016; constexpr uint64_t kPhysicalPrivacyScreenToggle = 0x00000017; +constexpr uint64_t kPhysicalMicrophoneMuteToggle = 0x00000018; constexpr uint64_t kPhysicalSleep = 0x00010082; constexpr uint64_t kPhysicalWakeUp = 0x00010083; constexpr uint64_t kPhysicalDisplayToggleIntExt = 0x000100b5; diff --git a/engine/src/flutter/shell/platform/linux/fl_key_channel_responder.cc b/engine/src/flutter/shell/platform/linux/fl_key_channel_responder.cc index 8f9946691cd..b00f1f7ae86 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_channel_responder.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_channel_responder.cc @@ -19,6 +19,7 @@ static constexpr char kKeyCodeKey[] = "keyCode"; static constexpr char kScanCodeKey[] = "scanCode"; static constexpr char kModifiersKey[] = "modifiers"; static constexpr char kToolkitKey[] = "toolkit"; +static constexpr char kSpecifiedLogicalKey[] = "specifiedLogicalKey"; static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues"; static constexpr char kGtkToolkit[] = "gtk"; @@ -115,6 +116,7 @@ G_DEFINE_TYPE_WITH_CODE( static void fl_key_channel_responder_handle_event( FlKeyResponder* responder, FlKeyEvent* event, + uint64_t specified_logical_key, FlKeyResponderAsyncCallback callback, gpointer user_data); @@ -203,6 +205,7 @@ FlKeyChannelResponder* fl_key_channel_responder_new( static void fl_key_channel_responder_handle_event( FlKeyResponder* responder, FlKeyEvent* event, + uint64_t specified_logical_key, FlKeyResponderAsyncCallback callback, gpointer user_data) { FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(responder); @@ -273,6 +276,11 @@ static void fl_key_channel_responder_handle_event( fl_value_new_int(unicode_scarlar_values)); } + if (specified_logical_key != 0) { + fl_value_set_string_take(message, kSpecifiedLogicalKey, + fl_value_new_int(specified_logical_key)); + } + FlKeyChannelUserData* data = fl_key_channel_user_data_new(self, callback, user_data); // Send the message off to the framework for handling (or not). diff --git a/engine/src/flutter/shell/platform/linux/fl_key_channel_responder_test.cc b/engine/src/flutter/shell/platform/linux/fl_key_channel_responder_test.cc index 75dcd17b4b3..d44848c2693 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_channel_responder_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_channel_responder_test.cc @@ -203,3 +203,30 @@ TEST(FlKeyChannelResponderTest, TestKeyEventHandledByFramework) { // Blocks here until echo_response_cb is called. g_main_loop_run(loop); } + +TEST(FlKeyChannelResponderTest, UseSpecifiedLogicalKey) { + g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0); + + g_autoptr(FlEngine) engine = make_mock_engine(); + g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine); + FlKeyChannelResponderMock mock{ + .value_converter = echo_response_cb, + .channel_name = "test/echo", + }; + g_autoptr(FlKeyResponder) responder = + FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock)); + + fl_key_responder_handle_event( + responder, + fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04, 0x0, nullptr, + false), + responder_callback, loop, 888); + expected_handled = TRUE; + expected_value = + "{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, " + "keyCode: 65, modifiers: 0, unicodeScalarValues: 65, " + "specifiedLogicalKey: 888}"; + + // Blocks here until echo_response_cb is called. + g_main_loop_run(loop); +} diff --git a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc index cf8fdba3e20..9aec7e40a88 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder.cc @@ -212,6 +212,7 @@ G_DEFINE_TYPE_WITH_CODE( static void fl_key_embedder_responder_handle_event( FlKeyResponder* responder, FlKeyEvent* event, + uint64_t specified_logical_key, FlKeyResponderAsyncCallback callback, gpointer user_data); @@ -698,6 +699,7 @@ static void synchronize_lock_states_loop_body(gpointer key, static void fl_key_embedder_responder_handle_event_impl( FlKeyResponder* responder, FlKeyEvent* event, + uint64_t specified_logical_key, FlKeyResponderAsyncCallback callback, gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); @@ -706,7 +708,9 @@ static void fl_key_embedder_responder_handle_event_impl( g_return_if_fail(callback != nullptr); const uint64_t physical_key = event_to_physical_key(event); - const uint64_t logical_key = event_to_logical_key(event); + const uint64_t logical_key = specified_logical_key != 0 + ? specified_logical_key + : event_to_logical_key(event); const double timestamp = event_to_timestamp(event); const bool is_down_event = event->is_press; @@ -777,12 +781,13 @@ static void fl_key_embedder_responder_handle_event_impl( static void fl_key_embedder_responder_handle_event( FlKeyResponder* responder, FlKeyEvent* event, + uint64_t specified_logical_key, FlKeyResponderAsyncCallback callback, gpointer user_data) { FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder); self->sent_any_events = false; - fl_key_embedder_responder_handle_event_impl(responder, event, callback, - user_data); + fl_key_embedder_responder_handle_event_impl( + responder, event, specified_logical_key, callback, user_data); if (!self->sent_any_events) { self->send_key_event(&empty_event, nullptr, nullptr); } diff --git a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc index 7c1ca368e48..2e0ed9a8b65 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_embedder_responder_test.cc @@ -194,8 +194,6 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { invoke_record_callback_and_verify(record, TRUE, &user_data); g_ptr_array_clear(g_call_records); - // Skip testing key repeats, which is not present on GDK. - // Key up fl_key_responder_handle_event( responder, @@ -261,6 +259,41 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { g_object_unref(responder); } +// Basic key presses, but uses the specified logical key if it is not 0. +TEST(FlKeyEmbedderResponderTest, UsesSpecifiedLogicalKey) { + EXPECT_EQ(g_call_records, nullptr); + g_call_records = g_ptr_array_new_with_free_func(g_object_unref); + FlKeyResponder* responder = FL_KEY_RESPONDER( + fl_key_embedder_responder_new(record_calls_in(g_call_records))); + int user_data = 123; // Arbitrary user data + + FlKeyEmbedderCallRecord* record; + + // On an AZERTY keyboard, press physical key 1, and release. + // Key down + fl_key_responder_handle_event( + responder, + fl_key_event_new_by_mock(12345, kPress, GDK_KEY_ampersand, kKeyCodeDigit1, + 0, kIsNotModifier), + verify_response_handled, &user_data, kLogicalDigit1); + + EXPECT_EQ(g_call_records->len, 1u); + record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); + EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent)); + EXPECT_EQ(record->event->timestamp, 12345000); + EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); + EXPECT_EQ(record->event->physical, kPhysicalDigit1); + EXPECT_EQ(record->event->logical, kLogicalDigit1); + EXPECT_STREQ(record->event->character, "&"); + EXPECT_EQ(record->event->synthesized, false); + + invoke_record_callback_and_verify(record, TRUE, &user_data); + g_ptr_array_clear(g_call_records); + + clear_g_call_records(); + g_object_unref(responder); +} + // Press Shift, key A, then release Shift, key A. TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { EXPECT_EQ(g_call_records, nullptr); diff --git a/engine/src/flutter/shell/platform/linux/fl_key_event.cc b/engine/src/flutter/shell/platform/linux/fl_key_event.cc index 2e3c0104579..440bedd2c1b 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_event.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_event.cc @@ -33,6 +33,7 @@ FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* raw_event) { result->keyval = event->keyval; result->state = event->state; result->string = clone_string(event->string); + result->group = event->group; result->origin = event; result->dispose_origin = dispose_origin_from_gdk_event; diff --git a/engine/src/flutter/shell/platform/linux/fl_key_event.h b/engine/src/flutter/shell/platform/linux/fl_key_event.h index 2fd6240b240..82a678a443e 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_event.h +++ b/engine/src/flutter/shell/platform/linux/fl_key_event.h @@ -39,6 +39,8 @@ typedef struct _FlKeyEvent { guint keyval; // Modifier state. int state; + // Keyboard group. + guint8 group; // String, null-terminated. // // Can be nullptr. diff --git a/engine/src/flutter/shell/platform/linux/fl_key_responder.cc b/engine/src/flutter/shell/platform/linux/fl_key_responder.cc index c5ac80d290d..269beab8b0c 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_responder.cc +++ b/engine/src/flutter/shell/platform/linux/fl_key_responder.cc @@ -11,11 +11,12 @@ static void fl_key_responder_default_init(FlKeyResponderInterface* iface) {} void fl_key_responder_handle_event(FlKeyResponder* self, FlKeyEvent* event, FlKeyResponderAsyncCallback callback, - gpointer user_data) { + gpointer user_data, + uint64_t specified_logical_key) { g_return_if_fail(FL_IS_KEY_RESPONDER(self)); g_return_if_fail(event != nullptr); g_return_if_fail(callback != nullptr); - FL_KEY_RESPONDER_GET_IFACE(self)->handle_event(self, event, callback, - user_data); + FL_KEY_RESPONDER_GET_IFACE(self)->handle_event( + self, event, specified_logical_key, callback, user_data); } diff --git a/engine/src/flutter/shell/platform/linux/fl_key_responder.h b/engine/src/flutter/shell/platform/linux/fl_key_responder.h index a21620675d0..756bc3206f7 100644 --- a/engine/src/flutter/shell/platform/linux/fl_key_responder.h +++ b/engine/src/flutter/shell/platform/linux/fl_key_responder.h @@ -53,6 +53,7 @@ struct _FlKeyResponderInterface { */ void (*handle_event)(FlKeyResponder* responder, FlKeyEvent* event, + uint64_t specified_logical_key, FlKeyResponderAsyncCallback callback, gpointer user_data); }; @@ -74,7 +75,8 @@ struct _FlKeyResponderInterface { void fl_key_responder_handle_event(FlKeyResponder* responder, FlKeyEvent* event, FlKeyResponderAsyncCallback callback, - gpointer user_data); + gpointer user_data, + uint64_t specified_logical_key = 0); G_END_DECLS diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc index 6eb379d5efe..06762cee629 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.cc @@ -4,13 +4,107 @@ #include "flutter/shell/platform/linux/fl_keyboard_manager.h" +#include #include #include +#include #include "flutter/shell/platform/linux/fl_key_channel_responder.h" #include "flutter/shell/platform/linux/fl_key_embedder_responder.h" +#include "flutter/shell/platform/linux/key_mapping.h" -/* Declare and define FlKeyboardPendingEvent */ +// 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(FlKeyboardPendingEvent, + fl_keyboard_pending_event, + FL, + KEYBOARD_PENDING_EVENT, + GObject); + +#define FL_TYPE_KEYBOARD_MANAGER_USER_DATA \ + fl_keyboard_manager_user_data_get_type() +G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData, + fl_keyboard_manager_user_data, + FL, + KEYBOARD_MANAGER_USER_DATA, + GObject); + +/* End declarations */ + +namespace { + +// The maxiumum keycode in a derived layout. +// +// Although X supports higher keycodes, Flutter only cares about standard keys, +// which are below this. +constexpr size_t kLayoutSize = 128; +// Describes the derived layout of a keyboard group. +// +// Maps from keycode to logical key. Value being 0 stands for empty. +typedef std::array DerivedGroupLayout; +// Describes the derived layout of the entire keyboard. +// +// Maps from group ID to group layout. +typedef std::map DerivedLayout; + +// Context variables for the foreach call used to dispatch events to responders. +typedef struct { + FlKeyEvent* event; + uint64_t specified_logical_key; + FlKeyboardManagerUserData* user_data; +} DispatchToResponderLoopContext; + +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 + +static uint64_t get_logical_key_from_layout(const FlKeyEvent* event, + const DerivedLayout& layout) { + guint8 group = event->group; + guint16 keycode = event->keycode; + if (keycode >= kLayoutSize) { + return 0; + } + + auto found_group_layout = layout.find(group); + if (found_group_layout != layout.end()) { + return found_group_layout->second[keycode]; + } + return 0; +} + +/* Define FlKeyboardPendingEvent */ /** * FlKeyboardPendingEvent: @@ -20,11 +114,6 @@ * This object is used by both the "pending_responds" list and the * "pending_redispatches" list. */ -G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent, - fl_keyboard_pending_event, - FL, - KEYBOARD_PENDING_EVENT, - GObject); struct _FlKeyboardPendingEvent { GObject parent_instance; @@ -87,7 +176,7 @@ static uint64_t fl_keyboard_manager_get_event_hash(FlKeyEvent* event) { // the sequence ID, and the number of responders that will reply. // // This will acquire the ownership of the event. -FlKeyboardPendingEvent* fl_keyboard_pending_event_new( +static FlKeyboardPendingEvent* fl_keyboard_pending_event_new( std::unique_ptr event, uint64_t sequence_id, size_t to_reply) { @@ -102,20 +191,13 @@ FlKeyboardPendingEvent* fl_keyboard_pending_event_new( return self; } -/* Declare and define FlKeyboardManagerUserData */ +/* Define FlKeyboardManagerUserData */ /** * FlKeyboardManagerUserData: * The user_data used when #FlKeyboardManager sends event to * responders. */ -#define FL_TYPE_KEYBOARD_MANAGER_USER_DATA \ - fl_keyboard_manager_user_data_get_type() -G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData, - fl_keyboard_manager_user_data, - FL, - KEYBOARD_MANAGER_USER_DATA, - GObject); struct _FlKeyboardManagerUserData { GObject parent_instance; @@ -125,16 +207,6 @@ struct _FlKeyboardManagerUserData { uint64_t sequence_id; }; -namespace { - -// Context variables for the foreach call used to dispatch events to responders. -typedef struct { - FlKeyEvent* event; - FlKeyboardManagerUserData* user_data; -} DispatchToResponderLoopContext; - -} // namespace - G_DEFINE_TYPE(FlKeyboardManagerUserData, fl_keyboard_manager_user_data, G_TYPE_OBJECT) @@ -158,7 +230,7 @@ static void fl_keyboard_manager_user_data_init( FlKeyboardManagerUserData* self) {} // Creates a new FlKeyboardManagerUserData private class with all information. -FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new( +static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new( FlKeyboardManager* manager, uint64_t sequence_id) { FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA( @@ -199,6 +271,22 @@ struct _FlKeyboardManager { // 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. + std::unique_ptr derived_layout; + // A static map from keycodes to all layout goals. + // + // It is set up when the manager is initialized and is not changed ever after. + std::unique_ptr> keycode_to_goals; + // A static map from logical keys to all mandatory layout goals. + // + // It is set up when the manager is initialized and is not changed ever after. + std::unique_ptr> + logical_to_mandatory_goals; }; G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT); @@ -209,11 +297,44 @@ 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) {} +static void fl_keyboard_manager_init(FlKeyboardManager* self) { + self->derived_layout = std::make_unique(); + + self->keycode_to_goals = + std::make_unique>(); + self->logical_to_mandatory_goals = + std::make_unique>(); + for (const LayoutGoal& goal : layout_goals) { + (*self->keycode_to_goals)[goal.keycode] = &goal; + if (goal.mandatory) { + (*self->logical_to_mandatory_goals)[goal.logical_key] = &goal; + } + } + + self->responder_list = g_ptr_array_new_with_free_func(g_object_unref); + + self->pending_responds = g_ptr_array_new(); + self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref); + + self->last_sequence_id = 1; +} static void fl_keyboard_manager_dispose(GObject* object) { FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object); + if (self->view_delegate != nullptr) { + fl_keyboard_view_delegate_subscribe_to_layout_change(self->view_delegate, + nullptr); + g_object_remove_weak_pointer( + G_OBJECT(self->view_delegate), + reinterpret_cast(&(self->view_delegate))); + self->view_delegate = nullptr; + } + + self->derived_layout.reset(); + self->keycode_to_goals.reset(); + self->logical_to_mandatory_goals.reset(); + g_ptr_array_free(self->responder_list, TRUE); g_ptr_array_set_free_func(self->pending_responds, g_object_unref); g_ptr_array_free(self->pending_responds, TRUE); @@ -227,10 +348,10 @@ static void fl_keyboard_manager_dispose(GObject* 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. -gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack, - gconstpointer needle, - GEqualFunc equal_func, - guint* index_) { +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) { @@ -292,6 +413,7 @@ static void responder_handle_event_callback(bool handled, FlKeyboardManagerUserData* user_data = FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr); FlKeyboardManager* self = user_data->manager; + g_return_if_fail(self->view_delegate != nullptr); guint result_index = -1; gboolean found = g_ptr_array_find_with_equal_func1( @@ -323,6 +445,93 @@ static void responder_handle_event_callback(bool handled, } } +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) { + guint8 group = event->group; + if (self->derived_layout->find(group) != self->derived_layout->end()) { + return; + } + if (self->keycode_to_goals->find(event->keycode) == + self->keycode_to_goals->end()) { + return; + } + + DerivedGroupLayout& layout = (*self->derived_layout)[group]; + + // Clone all mandatory goals. Each goal is removed from this cloned map when + // fulfilled, and the remaining ones will be assigned to a default position. + std::map remaining_mandatory_goals = + *self->logical_to_mandatory_goals; + +#ifdef DEBUG_PRINT_LAYOUT + std::string debug_layout_data; + for (uint16_t keycode = 0; keycode < 128; keycode += 1) { + std::vector this_key_clues = { + convert_key_to_char(self->view_delegate, keycode, group, 0), + convert_key_to_char(self->view_delegate, keycode, group, 1), // Shift + }; + debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0], + this_key_clues[1]); + } +#endif + + // It's important to only traverse layout goals instead of all keycodes. + // Some key codes outside of the standard keyboard also gives alpha-numeric + // letters, and will therefore take over mandatory goals from standard + // keyboard keys if they come first. Example: French keyboard digit 1. + for (const LayoutGoal& keycode_goal : layout_goals) { + uint16_t keycode = keycode_goal.keycode; + std::vector this_key_clues = { + convert_key_to_char(self->view_delegate, keycode, group, 0), + convert_key_to_char(self->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(layout[keycode] == 0); + layout[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 (layout[keycode] == 0 && !has_any_eascii) { + auto found_us_layout = self->keycode_to_goals->find(keycode); + if (found_us_layout != self->keycode_to_goals->end()) { + layout[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; + layout[goal->keycode] = goal->logical_key; + } +} + FlKeyboardManager* fl_keyboard_manager_new( FlKeyboardViewDelegate* view_delegate) { g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr); @@ -331,12 +540,9 @@ FlKeyboardManager* fl_keyboard_manager_new( g_object_new(fl_keyboard_manager_get_type(), nullptr)); self->view_delegate = view_delegate; - 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; + g_object_add_weak_pointer( + G_OBJECT(view_delegate), + reinterpret_cast(&(self->view_delegate))); // The embedder responder must be added before the channel responder. g_ptr_array_add( @@ -344,6 +550,7 @@ FlKeyboardManager* fl_keyboard_manager_new( FL_KEY_RESPONDER(fl_key_embedder_responder_new( [self](const FlutterKeyEvent* event, FlutterKeyEventCallback callback, void* user_data) { + g_return_if_fail(self->view_delegate != nullptr); fl_keyboard_view_delegate_send_key_event(self->view_delegate, event, callback, user_data); }))); @@ -351,6 +558,8 @@ FlKeyboardManager* fl_keyboard_manager_new( FL_KEY_RESPONDER(fl_key_channel_responder_new( fl_keyboard_view_delegate_get_messenger(view_delegate)))); + fl_keyboard_view_delegate_subscribe_to_layout_change( + self->view_delegate, [self]() { self->derived_layout->clear(); }); return self; } @@ -360,15 +569,18 @@ static void dispatch_to_responder(gpointer responder_data, DispatchToResponderLoopContext* context = reinterpret_cast(foreach_data_ptr); FlKeyResponder* responder = FL_KEY_RESPONDER(responder_data); - fl_key_responder_handle_event(responder, context->event, - responder_handle_event_callback, - context->user_data); + fl_key_responder_handle_event( + responder, context->event, responder_handle_event_callback, + context->user_data, context->specified_logical_key); } 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); + g_return_val_if_fail(self->view_delegate != nullptr, FALSE); + + guarantee_layout(self, event); uint64_t incoming_hash = fl_keyboard_manager_get_event_hash(event); if (fl_keyboard_manager_remove_redispatched(self, incoming_hash)) { @@ -384,6 +596,8 @@ gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self, fl_keyboard_manager_user_data_new(self, pending->sequence_id); DispatchToResponderLoopContext data{ .event = event, + .specified_logical_key = + get_logical_key_from_layout(event, *self->derived_layout), .user_data = user_data, }; g_ptr_array_foreach(self->responder_list, dispatch_to_responder, &data); diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h index 0c001f71ef7..fb5d35d9bce 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager.h @@ -6,7 +6,6 @@ #define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_ #include -#include #include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h" diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc index b971ef12646..173b7bb9b32 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_manager_test.cc @@ -15,16 +15,35 @@ // 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(EVENT, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \ - EXPECT_EQ((EVENT)->type, (TYPE)); \ - EXPECT_EQ((EVENT)->physical, (PHYSICAL)); \ - EXPECT_EQ((EVENT)->logical, (LOGICAL)); \ - EXPECT_STREQ((EVENT)->character, (CHAR)); \ - EXPECT_EQ((EVENT)->synthesized, (SYNTHESIZED)); +#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::kLogicalBracketLeft; +using ::flutter::testing::keycodes::kLogicalComma; +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::kLogicalMinus; +using ::flutter::testing::keycodes::kLogicalParenthesisRight; +using ::flutter::testing::keycodes::kLogicalSemicolon; +using ::flutter::testing::keycodes::kLogicalUnderscore; + using ::flutter::testing::keycodes::kPhysicalKeyA; using ::flutter::testing::keycodes::kPhysicalKeyB; @@ -66,9 +85,26 @@ char* cloneString(const char* source) { constexpr guint16 kKeyCodeKeyA = 0x26u; constexpr guint16 kKeyCodeKeyB = 0x38u; +constexpr guint16 kKeyCodeKeyM = 0x3au; +constexpr guint16 kKeyCodeDigit1 = 0x0au; +constexpr guint16 kKeyCodeMinus = 0x14u; +constexpr guint16 kKeyCodeSemicolon = 0x2fu; +constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u; static constexpr char kKeyEventChannelName[] = "flutter/keyevent"; +// All key clues for a keyboard layout. +// +// The index is (keyCode * 2 + hasShift), where each value is the character for +// this key (GTK only supports UTF-16.) Since the maximum keycode of interest +// is 128, it has a total of 256 entries.. +typedef std::array MockGroupLayoutData; +typedef std::vector MockLayoutData; + +extern const MockLayoutData kLayoutUs; +extern const MockLayoutData kLayoutRussian; +extern const MockLayoutData kLayoutFrench; + G_BEGIN_DECLS G_DECLARE_FINAL_TYPE(FlMockViewDelegate, @@ -194,6 +230,8 @@ struct _FlMockViewDelegate { EmbedderCallHandler embedder_handler; bool text_filter_result; RedispatchHandler redispatch_handler; + KeyboardLayoutNotifier layout_notifier; + const MockLayoutData* layout_data; }; static void fl_mock_view_keyboard_delegate_iface_init( @@ -257,12 +295,34 @@ static void fl_mock_view_keyboard_redispatch_event( } } +static void fl_mock_view_keyboard_subscribe_to_layout_change( + FlKeyboardViewDelegate* delegate, + KeyboardLayoutNotifier notifier) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(delegate); + self->layout_notifier = std::move(notifier); +} + +static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate, + const GdkKeymapKey* key) { + FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(delegate); + guint8 group = static_cast(key->group); + EXPECT_LT(group, self->layout_data->size()); + const MockGroupLayoutData* group_layout = (*self->layout_data)[group]; + EXPECT_TRUE(group_layout != nullptr); + EXPECT_TRUE(key->level == 0 || key->level == 1); + bool shift = key->level == 1; + return (*group_layout)[key->keycode * 2 + shift]; +} + static void fl_mock_view_keyboard_delegate_iface_init( FlKeyboardViewDelegateInterface* iface) { iface->send_key_event = fl_mock_view_keyboard_send_key_event; iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press; iface->get_messenger = fl_mock_view_keyboard_get_messenger; iface->redispatch_event = fl_mock_view_keyboard_redispatch_event; + iface->subscribe_to_layout_change = + fl_mock_view_keyboard_subscribe_to_layout_change; + iface->lookup_key = fl_mock_view_keyboard_lookup_key; } static FlMockViewDelegate* fl_mock_view_delegate_new() { @@ -292,6 +352,14 @@ static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self, self->redispatch_handler = std::move(handler); } +static void fl_mock_view_set_layout(FlMockViewDelegate* self, + const MockLayoutData* layout) { + self->layout_data = layout; + if (self->layout_notifier != nullptr) { + self->layout_notifier(); + } +} + /***** End FlMockViewDelegate *****/ // Return a newly allocated #FlKeyEvent that is a clone to the given #event @@ -311,13 +379,15 @@ static FlKeyEvent* fl_key_event_new_by_mock(bool is_press, guint keyval, guint16 keycode, int state, - gboolean is_modifier) { + gboolean is_modifier, + guint8 group = 0) { FlKeyEvent* event = g_new(FlKeyEvent, 1); event->is_press = is_press; event->time = 0; event->state = state; event->keyval = keyval; event->string = nullptr; + event->group = group; event->keycode = keycode; FlKeyEvent* origin_event = fl_key_event_clone_information_only(event); event->origin = origin_event; @@ -332,6 +402,7 @@ class KeyboardTester { respondToEmbedderCallsWith(false); respondToChannelCallsWith(false); respondToTextInputWith(false); + setLayout(kLayoutUs); manager_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_)); } @@ -451,6 +522,10 @@ class KeyboardTester { }); } + void setLayout(const MockLayoutData& layout) { + fl_mock_view_set_layout(view_, &layout); + } + private: FlMockViewDelegate* view_; FlKeyboardManager* manager_; @@ -504,8 +579,8 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) { EXPECT_EQ(manager_handled, true); EXPECT_EQ(redispatched.size(), 0u); EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeDown, - kPhysicalKeyA, kLogicalKeyA, "a", false); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "a", false); call_records[0].callback(true); tester.flushChannelMessages(); @@ -521,7 +596,7 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) { EXPECT_EQ(manager_handled, true); EXPECT_EQ(redispatched.size(), 0u); EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeUp, kPhysicalKeyA, + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, kLogicalKeyA, nullptr, false); // Dispatch another key event @@ -532,8 +607,8 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) { EXPECT_EQ(manager_handled, true); EXPECT_EQ(redispatched.size(), 0u); EXPECT_EQ(call_records.size(), 2u); - EXPECT_KEY_EVENT(call_records[1].event, kFlutterKeyEventTypeDown, - kPhysicalKeyB, kLogicalKeyB, "b", false); + 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); @@ -584,8 +659,8 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) { tester.flushChannelMessages(); EXPECT_EQ(manager_handled, true); EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeDown, - kPhysicalKeyA, kLogicalKeyA, "a", false); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, + kLogicalKeyA, "a", false); EXPECT_EQ(redispatched.size(), 0u); call_records.clear(); @@ -600,7 +675,7 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) { tester.flushChannelMessages(); EXPECT_EQ(manager_handled, true); EXPECT_EQ(call_records.size(), 1u); - EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeUp, kPhysicalKeyA, + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, kLogicalKeyA, nullptr, false); EXPECT_EQ(redispatched.size(), 1u); call_records.clear(); @@ -712,4 +787,285 @@ TEST(FlKeyboardManagerTest, TextInputPluginReturnsTrue) { EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); } +TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) { + KeyboardTester tester; + + std::vector call_records; + tester.recordEmbedderCallsTo(call_records); + + auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) { + fl_keyboard_manager_handle_event( + tester.manager(), + fl_key_event_new_by_mock(true, keyval, keycode, 0, false, group)); + fl_keyboard_manager_handle_event( + tester.manager(), + fl_key_event_new_by_mock(false, keyval, keycode, 0, false, group)); + }; + + /* US keyboard layout */ + + sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA + VERIFY_DOWN(kLogicalKeyA, "a"); + + sendTap(kKeyCodeKeyA, GDK_KEY_A, 0); // Shift-KeyA + VERIFY_DOWN(kLogicalKeyA, "A"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0); // Shift-Digit1 + VERIFY_DOWN(kLogicalDigit1, "!"); + + sendTap(kKeyCodeMinus, GDK_KEY_minus, 0); // Minus + VERIFY_DOWN(kLogicalMinus, "-"); + + sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0); // Shift-Minus + VERIFY_DOWN(kLogicalUnderscore, "_"); + + /* French keyboard layout, group 3, which is when the input method is showing + * "Fr" */ + + tester.setLayout(kLayoutFrench); + + sendTap(kKeyCodeKeyA, GDK_KEY_q, 3); // KeyA + VERIFY_DOWN(kLogicalKeyQ, "q"); + + sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3); // Shift-KeyA + VERIFY_DOWN(kLogicalKeyQ, "Q"); + + sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3); // ; but prints M + VERIFY_DOWN(kLogicalKeyM, "m"); + + sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3); // M but prints , + VERIFY_DOWN(kLogicalComma, ","); + + sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3); // Digit1 + VERIFY_DOWN(kLogicalDigit1, "&"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 3); // Shift-Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3); // Minus + VERIFY_DOWN(kLogicalParenthesisRight, ")"); + + sendTap(kKeyCodeMinus, GDK_KEY_degree, 3); // Shift-Minus + VERIFY_DOWN(static_cast(L'°'), "°"); + + /* French keyboard layout, group 0, which is pressing the "extra key for + * triggering input method" key once after switching to French IME. */ + + sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA + VERIFY_DOWN(kLogicalKeyA, "a"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + /* Russian keyboard layout, group 2 */ + tester.setLayout(kLayoutRussian); + + sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2); // KeyA + VERIFY_DOWN(kLogicalKeyA, "ф"); + + sendTap(kKeyCodeDigit1, GDK_KEY_1, 2); // Shift-Digit1 + VERIFY_DOWN(kLogicalDigit1, "1"); + + sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2); + VERIFY_DOWN(kLogicalBracketLeft, "х"); + + /* Russian keyboard layout, group 0 */ + sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA + VERIFY_DOWN(kLogicalKeyA, "a"); + + sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0); + VERIFY_DOWN(kLogicalBracketLeft, "["); +} + +// 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/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.cc b/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.cc index e4fd6f87aae..b2a724acfa4 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.cc +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.cc @@ -48,3 +48,19 @@ void fl_keyboard_view_delegate_redispatch_event( return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->redispatch_event( self, std::move(event)); } + +void fl_keyboard_view_delegate_subscribe_to_layout_change( + FlKeyboardViewDelegate* self, + KeyboardLayoutNotifier notifier) { + g_return_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self)); + + return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->subscribe_to_layout_change( + self, std::move(notifier)); +} + +guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* self, + const GdkKeymapKey* key) { + g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), 0); + + return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->lookup_key(self, key); +} diff --git a/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.h b/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.h index c6bcd97c369..041cb0029e1 100644 --- a/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.h +++ b/engine/src/flutter/shell/platform/linux/fl_keyboard_view_delegate.h @@ -7,12 +7,15 @@ #include #include +#include #include #include "flutter/shell/platform/embedder/embedder.h" #include "flutter/shell/platform/linux/fl_key_event.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h" +typedef std::function KeyboardLayoutNotifier; + G_BEGIN_DECLS G_DECLARE_INTERFACE(FlKeyboardViewDelegate, @@ -45,6 +48,12 @@ struct _FlKeyboardViewDelegateInterface { void (*redispatch_event)(FlKeyboardViewDelegate* delegate, std::unique_ptr event); + + void (*subscribe_to_layout_change)(FlKeyboardViewDelegate* delegate, + KeyboardLayoutNotifier notifier); + + guint (*lookup_key)(FlKeyboardViewDelegate* view_delegate, + const GdkKeymapKey* key); }; /** @@ -100,6 +109,13 @@ void fl_keyboard_view_delegate_redispatch_event( FlKeyboardViewDelegate* delegate, std::unique_ptr event); +void fl_keyboard_view_delegate_subscribe_to_layout_change( + FlKeyboardViewDelegate* delegate, + KeyboardLayoutNotifier notifier); + +guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* delegate, + const GdkKeymapKey* key); + G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_VIEW_DELEGATE_H_ diff --git a/engine/src/flutter/shell/platform/linux/fl_view.cc b/engine/src/flutter/shell/platform/linux/fl_view.cc index 71fe1f12163..928e31975cc 100644 --- a/engine/src/flutter/shell/platform/linux/fl_view.cc +++ b/engine/src/flutter/shell/platform/linux/fl_view.cc @@ -56,6 +56,10 @@ struct _FlView { // Tracks whether mouse pointer is inside the view. gboolean pointer_inside; + + /* FlKeyboardViewDelegate related properties */ + KeyboardLayoutNotifier keyboard_layout_notifier; + GdkKeymap* keymap; }; typedef struct _FlViewChild { @@ -250,15 +254,18 @@ static void fl_view_keyboard_delegate_iface_init( fl_engine_send_key_event(self->engine, event, callback, user_data); }; }; + iface->text_filter_key_press = [](FlKeyboardViewDelegate* view_delegate, FlKeyEvent* event) { FlView* self = FL_VIEW(view_delegate); return fl_text_input_plugin_filter_keypress(self->text_input_plugin, event); }; + iface->get_messenger = [](FlKeyboardViewDelegate* view_delegate) { FlView* self = FL_VIEW(view_delegate); return fl_engine_get_binary_messenger(self->engine); }; + iface->redispatch_event = [](FlKeyboardViewDelegate* view_delegate, std::unique_ptr in_event) { FlKeyEvent* event = in_event.release(); @@ -268,6 +275,18 @@ static void fl_view_keyboard_delegate_iface_init( gdk_event_put(gdk_event); fl_key_event_dispose(event); }; + + iface->subscribe_to_layout_change = [](FlKeyboardViewDelegate* view_delegate, + KeyboardLayoutNotifier notifier) { + FlView* self = FL_VIEW(view_delegate); + self->keyboard_layout_notifier = std::move(notifier); + }; + + iface->lookup_key = [](FlKeyboardViewDelegate* view_delegate, + const GdkKeymapKey* key) -> guint { + FlView* self = FL_VIEW(view_delegate); + return gdk_keymap_lookup_key(self->keymap, key); + }; } // Signal handler for GtkWidget::button-press-event @@ -386,6 +405,14 @@ static gboolean leave_notify_event_cb(GtkWidget* widget, return TRUE; } +static void keymap_keys_changed_cb(GdkKeymap* self, FlView* view) { + if (view->keyboard_layout_notifier == nullptr) { + return; + } + + view->keyboard_layout_notifier(); +} + static void fl_view_constructed(GObject* object) { FlView* self = FL_VIEW(object); @@ -396,6 +423,9 @@ static void fl_view_constructed(GObject* object) { fl_engine_set_on_pre_engine_restart_handler( self->engine, on_pre_engine_restart_cb, self, nullptr); + // Must initialize the keymap before the keyboard. + self->keymap = gdk_keymap_get_for_display(gdk_display_get_default()); + // Create system channel handlers. FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine); self->accessibility_plugin = fl_accessibility_plugin_new(self); @@ -423,6 +453,8 @@ static void fl_view_constructed(GObject* object) { G_CALLBACK(enter_notify_event_cb), self); g_signal_connect(self->event_box, "leave-notify-event", G_CALLBACK(leave_notify_event_cb), self); + g_signal_connect(self->keymap, "keys-changed", + G_CALLBACK(keymap_keys_changed_cb), self); } static void fl_view_set_property(GObject* object, @@ -710,6 +742,8 @@ static void fl_view_remove(GtkContainer* container, GtkWidget* widget) { if (widget == GTK_WIDGET(self->event_box)) { g_clear_object(&self->event_box); } + + g_clear_object(&self->keymap); } // Implements GtkContainer::forall diff --git a/engine/src/flutter/shell/platform/linux/key_mapping.cc b/engine/src/flutter/shell/platform/linux/key_mapping.cc index 014ba4a7c96..ed08c3512b3 100644 --- a/engine/src/flutter/shell/platform/linux/key_mapping.cc +++ b/engine/src/flutter/shell/platform/linux/key_mapping.cc @@ -207,6 +207,7 @@ std::map xkb_to_physical_key_map = { {0x000000f2, 0x000c0207}, // save {0x000000f3, 0x000c01a7}, // launchDocuments {0x000000fc, 0x000c0075}, // brightnessAuto + {0x00000100, 0x00000018}, // microphoneMuteToggle {0x0000016e, 0x000c0060}, // info {0x00000172, 0x000c008d}, // programGuide {0x0000017a, 0x000c0061}, // closedCaptionToggle @@ -458,6 +459,57 @@ void initialize_lock_bit_to_checked_keys(GHashTable* table) { data->primary_logical_key = 0x0010000010a; // numLock } +const std::vector layout_goals = { + LayoutGoal{0x30, 0x22, false}, // Quote + LayoutGoal{0x3b, 0x2c, false}, // Comma + LayoutGoal{0x14, 0x2d, false}, // Minus + LayoutGoal{0x3c, 0x2e, false}, // Period + LayoutGoal{0x3d, 0x2f, false}, // Slash + LayoutGoal{0x13, 0x30, true}, // Digit0 + LayoutGoal{0x0a, 0x31, true}, // Digit1 + LayoutGoal{0x0b, 0x32, true}, // Digit2 + LayoutGoal{0x0c, 0x33, true}, // Digit3 + LayoutGoal{0x0d, 0x34, true}, // Digit4 + LayoutGoal{0x0e, 0x35, true}, // Digit5 + LayoutGoal{0x0f, 0x36, true}, // Digit6 + LayoutGoal{0x10, 0x37, true}, // Digit7 + LayoutGoal{0x11, 0x38, true}, // Digit8 + LayoutGoal{0x12, 0x39, true}, // Digit9 + LayoutGoal{0x2f, 0x3b, false}, // Semicolon + LayoutGoal{0x15, 0x3d, false}, // Equal + LayoutGoal{0x22, 0x5b, false}, // BracketLeft + LayoutGoal{0x33, 0x5c, false}, // Backslash + LayoutGoal{0x23, 0x5d, false}, // BracketRight + LayoutGoal{0x31, 0x60, false}, // Backquote + LayoutGoal{0x26, 0x61, true}, // KeyA + LayoutGoal{0x38, 0x62, true}, // KeyB + LayoutGoal{0x36, 0x63, true}, // KeyC + LayoutGoal{0x28, 0x64, true}, // KeyD + LayoutGoal{0x1a, 0x65, true}, // KeyE + LayoutGoal{0x29, 0x66, true}, // KeyF + LayoutGoal{0x2a, 0x67, true}, // KeyG + LayoutGoal{0x2b, 0x68, true}, // KeyH + LayoutGoal{0x1f, 0x69, true}, // KeyI + LayoutGoal{0x2c, 0x6a, true}, // KeyJ + LayoutGoal{0x2d, 0x6b, true}, // KeyK + LayoutGoal{0x2e, 0x6c, true}, // KeyL + LayoutGoal{0x3a, 0x6d, true}, // KeyM + LayoutGoal{0x39, 0x6e, true}, // KeyN + LayoutGoal{0x20, 0x6f, true}, // KeyO + LayoutGoal{0x21, 0x70, true}, // KeyP + LayoutGoal{0x18, 0x71, true}, // KeyQ + LayoutGoal{0x1b, 0x72, true}, // KeyR + LayoutGoal{0x27, 0x73, true}, // KeyS + LayoutGoal{0x1c, 0x74, true}, // KeyT + LayoutGoal{0x1e, 0x75, true}, // KeyU + LayoutGoal{0x37, 0x76, true}, // KeyV + LayoutGoal{0x19, 0x77, true}, // KeyW + LayoutGoal{0x35, 0x78, true}, // KeyX + LayoutGoal{0x1d, 0x79, true}, // KeyY + LayoutGoal{0x34, 0x7a, true}, // KeyZ + LayoutGoal{0x5e, 0x200000020, false}, // IntlBackslash +}; + const uint64_t kValueMask = 0x000ffffffff; const uint64_t kUnicodePlane = 0x00000000000; const uint64_t kGtkPlane = 0x01500000000; diff --git a/engine/src/flutter/shell/platform/linux/key_mapping.h b/engine/src/flutter/shell/platform/linux/key_mapping.h index 44fa39290cb..cd425c65288 100644 --- a/engine/src/flutter/shell/platform/linux/key_mapping.h +++ b/engine/src/flutter/shell/platform/linux/key_mapping.h @@ -8,6 +8,7 @@ #include #include #include +#include inline uint64_t gpointer_to_uint64(gpointer pointer) { return pointer == nullptr ? 0 : reinterpret_cast(pointer); @@ -36,4 +37,18 @@ extern const uint64_t kUnicodePlane; // The plane value for the private keys defined by the GTK embedding. extern const uint64_t kGtkPlane; +typedef struct { + // The key code for a key that prints `keyChar` in the US keyboard layout. + uint16_t keycode; + + // The logical key for this key. + uint64_t logical_key; + + // If the goal is mandatory, the keyboard manager will make sure to find a + // logical key for this character, falling back to the US keyboard layout. + bool mandatory; +} LayoutGoal; + +extern const std::vector layout_goals; + #endif // KEYBOARD_MAP_H_