diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index 6c9c91f102a0d..925c530a71348 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -885,3 +885,8 @@ void fl_key_embedder_responder_sync_modifiers_if_needed( synchronize_pressed_states_loop_body, &sync_state_context); } + +GHashTable* fl_key_embedder_responder_get_pressed_state( + FlKeyEmbedderResponder* self) { + return self->pressing_records; +} diff --git a/shell/platform/linux/fl_key_embedder_responder.h b/shell/platform/linux/fl_key_embedder_responder.h index d7b3af8c0b410..8937f97e2ec13 100644 --- a/shell/platform/linux/fl_key_embedder_responder.h +++ b/shell/platform/linux/fl_key_embedder_responder.h @@ -64,6 +64,16 @@ void fl_key_embedder_responder_sync_modifiers_if_needed( guint state, double event_time); +/** + * fl_key_embedder_responder_get_pressed_state: + * @responder: the #FlKeyEmbedderResponder 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_key_embedder_responder_get_pressed_state( + FlKeyEmbedderResponder* responder); + G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_H_ diff --git a/shell/platform/linux/fl_keyboard_manager.cc b/shell/platform/linux/fl_keyboard_manager.cc index fc3c8ca31b38a..46f70fa2401d2 100644 --- a/shell/platform/linux/fl_keyboard_manager.cc +++ b/shell/platform/linux/fl_keyboard_manager.cc @@ -12,11 +12,16 @@ #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" +#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(FlKeyboardPendingEvent, @@ -287,6 +292,9 @@ struct _FlKeyboardManager { // It is set up when the manager is initialized and is not changed ever after. std::unique_ptr> logical_to_mandatory_goals; + + // The channel used by the framework to query the keyboard pressed state. + FlMethodChannel* channel; }; G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT); @@ -532,7 +540,50 @@ static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) { } } +// Returns the keyboard pressed state. +FlMethodResponse* get_keyboard_state(FlKeyboardManager* self) { + g_autoptr(FlValue) result = fl_value_new_map(); + + GHashTable* pressing_records = + fl_keyboard_view_delegate_get_keyboard_state(self->view_delegate); + + g_hash_table_foreach( + pressing_records, + [](gpointer key, gpointer value, gpointer user_data) { + int64_t physical_key = reinterpret_cast(key); + int64_t logical_key = reinterpret_cast(value); + FlValue* fl_value_map = reinterpret_cast(user_data); + + fl_value_set_take(fl_value_map, fl_value_new_int(physical_key), + fl_value_new_int(logical_key)); + }, + result); + return FL_METHOD_RESPONSE(fl_method_success_response_new(result)); +} + +// Called when a method call on flutter/keyboard is received from Flutter. +static void method_call_handler(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + FlKeyboardManager* self = FL_KEYBOARD_MANAGER(user_data); + + const gchar* method = fl_method_call_get_name(method_call); + + g_autoptr(FlMethodResponse) response = nullptr; + if (strcmp(method, kGetKeyboardStateMethod) == 0) { + response = get_keyboard_state(self); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + g_autoptr(GError) error = nullptr; + if (!fl_method_call_respond(method_call, response, &error)) { + g_warning("Failed to send method call response: %s", error->message); + } +} + FlKeyboardManager* fl_keyboard_manager_new( + FlBinaryMessenger* messenger, FlKeyboardViewDelegate* view_delegate) { g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr); @@ -560,6 +611,13 @@ FlKeyboardManager* fl_keyboard_manager_new( fl_keyboard_view_delegate_subscribe_to_layout_change( self->view_delegate, [self]() { self->derived_layout->clear(); }); + + // Setup the flutter/keyboard channel. + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = + fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_handler, + self, nullptr); return self; } @@ -614,6 +672,8 @@ gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) { 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 = @@ -621,3 +681,13 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self, 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); +} diff --git a/shell/platform/linux/fl_keyboard_manager.h b/shell/platform/linux/fl_keyboard_manager.h index 688e49f972a92..84f4b574bd28e 100644 --- a/shell/platform/linux/fl_keyboard_manager.h +++ b/shell/platform/linux/fl_keyboard_manager.h @@ -44,6 +44,7 @@ G_DECLARE_FINAL_TYPE(FlKeyboardManager, * Returns: a new #FlKeyboardManager. */ FlKeyboardManager* fl_keyboard_manager_new( + FlBinaryMessenger* messenger, FlKeyboardViewDelegate* view_delegate); /** @@ -83,6 +84,15 @@ 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); + 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 index b408cb89eac00..de45994e94850 100644 --- a/shell/platform/linux/fl_keyboard_manager_test.cc +++ b/shell/platform/linux/fl_keyboard_manager_test.cc @@ -8,8 +8,18 @@ #include #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" +#include "flutter/shell/platform/linux/fl_binary_messenger_private.h" +#include "flutter/shell/platform/linux/fl_method_codec_private.h" +#include "flutter/shell/platform/linux/key_mapping.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h" +#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h" +#include "flutter/shell/platform/linux/testing/fl_test.h" +#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h" #include "flutter/shell/platform/linux/testing/mock_text_input_plugin.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 @@ -100,6 +110,10 @@ 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. // @@ -129,6 +143,19 @@ G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger, 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 @@ -322,6 +349,15 @@ static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate, return (*group_layout)[key->keycode * 2 + shift]; } +static GHashTable* fl_mock_view_keyboard_get_keyboard_state( + FlKeyboardViewDelegate* view_delegate) { + GHashTable* result = g_hash_table_new(g_direct_hash, g_direct_equal); + g_hash_table_insert(result, reinterpret_cast(kMockPhysicalKey), + reinterpret_cast(kMockLogicalKey)); + + return result; +} + static void fl_mock_view_keyboard_delegate_iface_init( FlKeyboardViewDelegateInterface* iface) { iface->send_key_event = fl_mock_view_keyboard_send_key_event; @@ -331,6 +367,7 @@ static void fl_mock_view_keyboard_delegate_iface_init( iface->subscribe_to_layout_change = fl_mock_view_keyboard_subscribe_to_layout_change; 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() { @@ -406,13 +443,16 @@ static FlKeyEvent* fl_key_event_new_by_mock(bool is_press, class KeyboardTester { public: KeyboardTester() { + ::testing::NiceMock messenger; + view_ = fl_mock_view_delegate_new(); respondToEmbedderCallsWith(false); respondToChannelCallsWith(false); respondToTextInputWith(false); setLayout(kLayoutUs); - manager_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_)); + manager_ = + fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(view_)); } ~KeyboardTester() { @@ -926,6 +966,47 @@ TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) { kLogicalShiftLeft); } +TEST(FlKeyboardManagerTest, GetPressedState) { + KeyboardTester tester; + tester.respondToTextInputWith(true); + + // Dispatch a key event. + fl_keyboard_manager_handle_event( + tester.manager(), + fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false)); + + GHashTable* pressedState = + fl_keyboard_manager_get_pressed_state(tester.manager()); + 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(FlKeyboardPluginTest, KeyboardChannelGetPressedState) { + ::testing::NiceMock messenger; + + g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new( + messenger, FL_KEYBOARD_VIEW_DELEGATE(fl_mock_view_delegate_new())); + EXPECT_NE(manager, nullptr); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(GBytes) message = fl_method_codec_encode_method_call( + FL_METHOD_CODEC(codec), kGetKeyboardStateMethod, nullptr, nullptr); + + g_autoptr(FlValue) response = fl_value_new_map(); + fl_value_set_take(response, fl_value_new_int(kMockPhysicalKey), + fl_value_new_int(kMockLogicalKey)); + EXPECT_CALL(messenger, + fl_binary_messenger_send_response( + ::testing::Eq(messenger), ::testing::_, + MethodSuccessResponse(response), ::testing::_)) + .WillOnce(::testing::Return(true)); + + messenger.ReceiveMessage(kKeyboardChannelName, message); +} + // The following layout data is generated using DEBUG_PRINT_LAYOUT. const MockGroupLayoutData kLayoutUs0{{ diff --git a/shell/platform/linux/fl_keyboard_view_delegate.cc b/shell/platform/linux/fl_keyboard_view_delegate.cc index b2a724acfa415..3c5fac95a8c4d 100644 --- a/shell/platform/linux/fl_keyboard_view_delegate.cc +++ b/shell/platform/linux/fl_keyboard_view_delegate.cc @@ -64,3 +64,10 @@ guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* self, return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->lookup_key(self, key); } + +GHashTable* fl_keyboard_view_delegate_get_keyboard_state( + FlKeyboardViewDelegate* self) { + g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), nullptr); + + return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->get_keyboard_state(self); +} diff --git a/shell/platform/linux/fl_keyboard_view_delegate.h b/shell/platform/linux/fl_keyboard_view_delegate.h index 041cb0029e143..6d66b12ca3537 100644 --- a/shell/platform/linux/fl_keyboard_view_delegate.h +++ b/shell/platform/linux/fl_keyboard_view_delegate.h @@ -54,6 +54,8 @@ struct _FlKeyboardViewDelegateInterface { guint (*lookup_key)(FlKeyboardViewDelegate* view_delegate, const GdkKeymapKey* key); + + GHashTable* (*get_keyboard_state)(FlKeyboardViewDelegate* delegate); }; /** @@ -116,6 +118,16 @@ void fl_keyboard_view_delegate_subscribe_to_layout_change( guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* delegate, const GdkKeymapKey* key); +/** + * fl_keyboard_view_delegate_get_keyboard_state: + * + * 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_view_delegate_get_keyboard_state( + FlKeyboardViewDelegate* delegate); + G_END_DECLS #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_VIEW_DELEGATE_H_ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 58f11ebf68b10..c1c5b547c6f6e 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -111,7 +111,7 @@ static void init_keyboard(FlView* self) { self->text_input_plugin = fl_text_input_plugin_new( messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(self)); self->keyboard_manager = - fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self)); + fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self)); } static void init_scrolling(FlView* self) { @@ -297,6 +297,12 @@ static void fl_view_keyboard_delegate_iface_init( g_return_val_if_fail(self->keymap != nullptr, 0); return gdk_keymap_lookup_key(self->keymap, key); }; + + iface->get_keyboard_state = + [](FlKeyboardViewDelegate* view_delegate) -> GHashTable* { + FlView* self = FL_VIEW(view_delegate); + return fl_view_get_keyboard_state(self); + }; } static void fl_view_scrolling_delegate_iface_init( @@ -709,3 +715,9 @@ void fl_view_set_textures(FlView* self, fl_gl_area_queue_render(self->gl_area, textures); } + +GHashTable* fl_view_get_keyboard_state(FlView* self) { + g_return_val_if_fail(FL_IS_VIEW(self), nullptr); + + return fl_keyboard_manager_get_pressed_state(self->keyboard_manager); +} diff --git a/shell/platform/linux/fl_view_private.h b/shell/platform/linux/fl_view_private.h index f9650227a88e5..dffc5114e5387 100644 --- a/shell/platform/linux/fl_view_private.h +++ b/shell/platform/linux/fl_view_private.h @@ -22,4 +22,13 @@ void fl_view_set_textures(FlView* view, GdkGLContext* context, GPtrArray* textures); +/** + * fl_view_get_keyboard_state: + * @view: an #FlView. + * + * 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_view_get_keyboard_state(FlView* view); + #endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_