Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f7adb9f

Browse files
authored
[Linux] Return keyboard pressed state (#42346)
## Description This PR updates the Linux engine in order to answer to keyboard pressed state queries from the framework (as implemented in flutter/flutter#122885). ## Related Issue Linux engine implementation for flutter/flutter#87391 ## Tests Adds 2 tests.
1 parent cb93477 commit f7adb9f

9 files changed

+218
-2
lines changed

shell/platform/linux/fl_key_embedder_responder.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -885,3 +885,8 @@ void fl_key_embedder_responder_sync_modifiers_if_needed(
885885
synchronize_pressed_states_loop_body,
886886
&sync_state_context);
887887
}
888+
889+
GHashTable* fl_key_embedder_responder_get_pressed_state(
890+
FlKeyEmbedderResponder* self) {
891+
return self->pressing_records;
892+
}

shell/platform/linux/fl_key_embedder_responder.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ void fl_key_embedder_responder_sync_modifiers_if_needed(
6464
guint state,
6565
double event_time);
6666

67+
/**
68+
* fl_key_embedder_responder_get_pressed_state:
69+
* @responder: the #FlKeyEmbedderResponder self.
70+
*
71+
* Returns the keyboard pressed state. The hash table contains one entry per
72+
* pressed keys, mapping from the logical key to the physical key.
73+
*/
74+
GHashTable* fl_key_embedder_responder_get_pressed_state(
75+
FlKeyEmbedderResponder* responder);
76+
6777
G_END_DECLS
6878

6979
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEY_EMBEDDER_RESPONDER_H_

shell/platform/linux/fl_keyboard_manager.cc

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@
1212
#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
1313
#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
1414
#include "flutter/shell/platform/linux/key_mapping.h"
15+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
16+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
1517

1618
// Turn on this flag to print complete layout data when switching IMEs. The data
1719
// is used in unit tests.
1820
#define DEBUG_PRINT_LAYOUT
1921

22+
static constexpr char kChannelName[] = "flutter/keyboard";
23+
static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
24+
2025
/* Declarations of private classes */
2126

2227
G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
@@ -287,6 +292,9 @@ struct _FlKeyboardManager {
287292
// It is set up when the manager is initialized and is not changed ever after.
288293
std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
289294
logical_to_mandatory_goals;
295+
296+
// The channel used by the framework to query the keyboard pressed state.
297+
FlMethodChannel* channel;
290298
};
291299

292300
G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT);
@@ -532,7 +540,50 @@ static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) {
532540
}
533541
}
534542

543+
// Returns the keyboard pressed state.
544+
FlMethodResponse* get_keyboard_state(FlKeyboardManager* self) {
545+
g_autoptr(FlValue) result = fl_value_new_map();
546+
547+
GHashTable* pressing_records =
548+
fl_keyboard_view_delegate_get_keyboard_state(self->view_delegate);
549+
550+
g_hash_table_foreach(
551+
pressing_records,
552+
[](gpointer key, gpointer value, gpointer user_data) {
553+
int64_t physical_key = reinterpret_cast<int64_t>(key);
554+
int64_t logical_key = reinterpret_cast<int64_t>(value);
555+
FlValue* fl_value_map = reinterpret_cast<FlValue*>(user_data);
556+
557+
fl_value_set_take(fl_value_map, fl_value_new_int(physical_key),
558+
fl_value_new_int(logical_key));
559+
},
560+
result);
561+
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
562+
}
563+
564+
// Called when a method call on flutter/keyboard is received from Flutter.
565+
static void method_call_handler(FlMethodChannel* channel,
566+
FlMethodCall* method_call,
567+
gpointer user_data) {
568+
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(user_data);
569+
570+
const gchar* method = fl_method_call_get_name(method_call);
571+
572+
g_autoptr(FlMethodResponse) response = nullptr;
573+
if (strcmp(method, kGetKeyboardStateMethod) == 0) {
574+
response = get_keyboard_state(self);
575+
} else {
576+
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
577+
}
578+
579+
g_autoptr(GError) error = nullptr;
580+
if (!fl_method_call_respond(method_call, response, &error)) {
581+
g_warning("Failed to send method call response: %s", error->message);
582+
}
583+
}
584+
535585
FlKeyboardManager* fl_keyboard_manager_new(
586+
FlBinaryMessenger* messenger,
536587
FlKeyboardViewDelegate* view_delegate) {
537588
g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr);
538589

@@ -560,6 +611,13 @@ FlKeyboardManager* fl_keyboard_manager_new(
560611

561612
fl_keyboard_view_delegate_subscribe_to_layout_change(
562613
self->view_delegate, [self]() { self->derived_layout->clear(); });
614+
615+
// Setup the flutter/keyboard channel.
616+
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
617+
self->channel =
618+
fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
619+
fl_method_channel_set_method_call_handler(self->channel, method_call_handler,
620+
self, nullptr);
563621
return self;
564622
}
565623

@@ -614,10 +672,22 @@ gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) {
614672
void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self,
615673
guint state,
616674
double event_time) {
675+
g_return_if_fail(FL_IS_KEYBOARD_MANAGER(self));
676+
617677
// The embedder responder is the first element in
618678
// FlKeyboardManager.responder_list.
619679
FlKeyEmbedderResponder* responder =
620680
FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
621681
fl_key_embedder_responder_sync_modifiers_if_needed(responder, state,
622682
event_time);
623683
}
684+
685+
GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* self) {
686+
g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), nullptr);
687+
688+
// The embedder responder is the first element in
689+
// FlKeyboardManager.responder_list.
690+
FlKeyEmbedderResponder* responder =
691+
FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0));
692+
return fl_key_embedder_responder_get_pressed_state(responder);
693+
}

shell/platform/linux/fl_keyboard_manager.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ G_DECLARE_FINAL_TYPE(FlKeyboardManager,
4444
* Returns: a new #FlKeyboardManager.
4545
*/
4646
FlKeyboardManager* fl_keyboard_manager_new(
47+
FlBinaryMessenger* messenger,
4748
FlKeyboardViewDelegate* view_delegate);
4849

4950
/**
@@ -83,6 +84,15 @@ void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager,
8384
guint state,
8485
double event_time);
8586

87+
/**
88+
* fl_keyboard_manager_get_pressed_state:
89+
* @manager: the #FlKeyboardManager self.
90+
*
91+
* Returns the keyboard pressed state. The hash table contains one entry per
92+
* pressed keys, mapping from the logical key to the physical key.*
93+
*/
94+
GHashTable* fl_keyboard_manager_get_pressed_state(FlKeyboardManager* manager);
95+
8696
G_END_DECLS
8797

8898
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_

shell/platform/linux/fl_keyboard_manager_test.cc

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,18 @@
88
#include <vector>
99

1010
#include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
11+
#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
12+
#include "flutter/shell/platform/linux/fl_method_codec_private.h"
13+
#include "flutter/shell/platform/linux/key_mapping.h"
1114
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
15+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_codec.h"
16+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
17+
#include "flutter/shell/platform/linux/testing/fl_test.h"
18+
#include "flutter/shell/platform/linux/testing/mock_binary_messenger.h"
1219
#include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h"
20+
#include "flutter/testing/testing.h"
21+
22+
#include "gmock/gmock.h"
1323
#include "gtest/gtest.h"
1424

1525
// Define compound `expect` in macros. If they were defined in functions, the
@@ -100,6 +110,10 @@ constexpr guint16 kKeyCodeSemicolon = 0x2fu;
100110
constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u;
101111

102112
static constexpr char kKeyEventChannelName[] = "flutter/keyevent";
113+
static constexpr char kKeyboardChannelName[] = "flutter/keyboard";
114+
static constexpr char kGetKeyboardStateMethod[] = "getKeyboardState";
115+
static constexpr uint64_t kMockPhysicalKey = 42;
116+
static constexpr uint64_t kMockLogicalKey = 42;
103117

104118
// All key clues for a keyboard layout.
105119
//
@@ -129,6 +143,19 @@ G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger,
129143

130144
G_END_DECLS
131145

146+
MATCHER_P(MethodSuccessResponse, result, "") {
147+
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
148+
g_autoptr(FlMethodResponse) response =
149+
fl_method_codec_decode_response(FL_METHOD_CODEC(codec), arg, nullptr);
150+
fl_method_response_get_result(response, nullptr);
151+
if (fl_value_equal(fl_method_response_get_result(response, nullptr),
152+
result)) {
153+
return true;
154+
}
155+
*result_listener << ::testing::PrintToString(response);
156+
return false;
157+
}
158+
132159
/***** FlMockKeyBinaryMessenger *****/
133160
/* Mock a binary messenger that only processes messages from the embedding on
134161
* 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,
322349
return (*group_layout)[key->keycode * 2 + shift];
323350
}
324351

352+
static GHashTable* fl_mock_view_keyboard_get_keyboard_state(
353+
FlKeyboardViewDelegate* view_delegate) {
354+
GHashTable* result = g_hash_table_new(g_direct_hash, g_direct_equal);
355+
g_hash_table_insert(result, reinterpret_cast<gpointer>(kMockPhysicalKey),
356+
reinterpret_cast<gpointer>(kMockLogicalKey));
357+
358+
return result;
359+
}
360+
325361
static void fl_mock_view_keyboard_delegate_iface_init(
326362
FlKeyboardViewDelegateInterface* iface) {
327363
iface->send_key_event = fl_mock_view_keyboard_send_key_event;
@@ -331,6 +367,7 @@ static void fl_mock_view_keyboard_delegate_iface_init(
331367
iface->subscribe_to_layout_change =
332368
fl_mock_view_keyboard_subscribe_to_layout_change;
333369
iface->lookup_key = fl_mock_view_keyboard_lookup_key;
370+
iface->get_keyboard_state = fl_mock_view_keyboard_get_keyboard_state;
334371
}
335372

336373
static FlMockViewDelegate* fl_mock_view_delegate_new() {
@@ -406,13 +443,16 @@ static FlKeyEvent* fl_key_event_new_by_mock(bool is_press,
406443
class KeyboardTester {
407444
public:
408445
KeyboardTester() {
446+
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
447+
409448
view_ = fl_mock_view_delegate_new();
410449
respondToEmbedderCallsWith(false);
411450
respondToChannelCallsWith(false);
412451
respondToTextInputWith(false);
413452
setLayout(kLayoutUs);
414453

415-
manager_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_));
454+
manager_ =
455+
fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(view_));
416456
}
417457

418458
~KeyboardTester() {
@@ -926,6 +966,47 @@ TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) {
926966
kLogicalShiftLeft);
927967
}
928968

969+
TEST(FlKeyboardManagerTest, GetPressedState) {
970+
KeyboardTester tester;
971+
tester.respondToTextInputWith(true);
972+
973+
// Dispatch a key event.
974+
fl_keyboard_manager_handle_event(
975+
tester.manager(),
976+
fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false));
977+
978+
GHashTable* pressedState =
979+
fl_keyboard_manager_get_pressed_state(tester.manager());
980+
EXPECT_EQ(g_hash_table_size(pressedState), 1u);
981+
982+
gpointer physical_key =
983+
g_hash_table_lookup(pressedState, uint64_to_gpointer(kPhysicalKeyA));
984+
EXPECT_EQ(gpointer_to_uint64(physical_key), kLogicalKeyA);
985+
}
986+
987+
TEST(FlKeyboardPluginTest, KeyboardChannelGetPressedState) {
988+
::testing::NiceMock<flutter::testing::MockBinaryMessenger> messenger;
989+
990+
g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new(
991+
messenger, FL_KEYBOARD_VIEW_DELEGATE(fl_mock_view_delegate_new()));
992+
EXPECT_NE(manager, nullptr);
993+
994+
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
995+
g_autoptr(GBytes) message = fl_method_codec_encode_method_call(
996+
FL_METHOD_CODEC(codec), kGetKeyboardStateMethod, nullptr, nullptr);
997+
998+
g_autoptr(FlValue) response = fl_value_new_map();
999+
fl_value_set_take(response, fl_value_new_int(kMockPhysicalKey),
1000+
fl_value_new_int(kMockLogicalKey));
1001+
EXPECT_CALL(messenger,
1002+
fl_binary_messenger_send_response(
1003+
::testing::Eq<FlBinaryMessenger*>(messenger), ::testing::_,
1004+
MethodSuccessResponse(response), ::testing::_))
1005+
.WillOnce(::testing::Return(true));
1006+
1007+
messenger.ReceiveMessage(kKeyboardChannelName, message);
1008+
}
1009+
9291010
// The following layout data is generated using DEBUG_PRINT_LAYOUT.
9301011

9311012
const MockGroupLayoutData kLayoutUs0{{

shell/platform/linux/fl_keyboard_view_delegate.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,10 @@ guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* self,
6464

6565
return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->lookup_key(self, key);
6666
}
67+
68+
GHashTable* fl_keyboard_view_delegate_get_keyboard_state(
69+
FlKeyboardViewDelegate* self) {
70+
g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), nullptr);
71+
72+
return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->get_keyboard_state(self);
73+
}

shell/platform/linux/fl_keyboard_view_delegate.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ struct _FlKeyboardViewDelegateInterface {
5454

5555
guint (*lookup_key)(FlKeyboardViewDelegate* view_delegate,
5656
const GdkKeymapKey* key);
57+
58+
GHashTable* (*get_keyboard_state)(FlKeyboardViewDelegate* delegate);
5759
};
5860

5961
/**
@@ -116,6 +118,16 @@ void fl_keyboard_view_delegate_subscribe_to_layout_change(
116118
guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* delegate,
117119
const GdkKeymapKey* key);
118120

121+
/**
122+
* fl_keyboard_view_delegate_get_keyboard_state:
123+
*
124+
* Returns the keyboard pressed state. The hash table contains one entry per
125+
* pressed keys, mapping from the logical key to the physical key.*
126+
*
127+
*/
128+
GHashTable* fl_keyboard_view_delegate_get_keyboard_state(
129+
FlKeyboardViewDelegate* delegate);
130+
119131
G_END_DECLS
120132

121133
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_VIEW_DELEGATE_H_

shell/platform/linux/fl_view.cc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ static void init_keyboard(FlView* self) {
116116
self->text_input_plugin = fl_text_input_plugin_new(
117117
messenger, im_context, FL_TEXT_INPUT_VIEW_DELEGATE(self));
118118
self->keyboard_manager =
119-
fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(self));
119+
fl_keyboard_manager_new(messenger, FL_KEYBOARD_VIEW_DELEGATE(self));
120120
}
121121

122122
static void init_scrolling(FlView* self) {
@@ -304,6 +304,12 @@ static void fl_view_keyboard_delegate_iface_init(
304304
g_return_val_if_fail(self->keymap != nullptr, 0);
305305
return gdk_keymap_lookup_key(self->keymap, key);
306306
};
307+
308+
iface->get_keyboard_state =
309+
[](FlKeyboardViewDelegate* view_delegate) -> GHashTable* {
310+
FlView* self = FL_VIEW(view_delegate);
311+
return fl_view_get_keyboard_state(self);
312+
};
307313
}
308314

309315
static void fl_view_scrolling_delegate_iface_init(
@@ -754,3 +760,9 @@ void fl_view_set_textures(FlView* self,
754760

755761
fl_gl_area_queue_render(self->gl_area, textures);
756762
}
763+
764+
GHashTable* fl_view_get_keyboard_state(FlView* self) {
765+
g_return_val_if_fail(FL_IS_VIEW(self), nullptr);
766+
767+
return fl_keyboard_manager_get_pressed_state(self->keyboard_manager);
768+
}

shell/platform/linux/fl_view_private.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,13 @@ void fl_view_set_textures(FlView* view,
2222
GdkGLContext* context,
2323
GPtrArray* textures);
2424

25+
/**
26+
* fl_view_get_keyboard_state:
27+
* @view: an #FlView.
28+
*
29+
* Returns the keyboard pressed state. The hash table contains one entry per
30+
* pressed keys, mapping from the logical key to the physical key.*
31+
*/
32+
GHashTable* fl_view_get_keyboard_state(FlView* view);
33+
2534
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_VIEW_PRIVATE_H_

0 commit comments

Comments
 (0)