From dce3fe62572bddeb4effd2e22a2a8e7c3176343b Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 9 Nov 2022 16:44:25 +0100 Subject: [PATCH] [Linux] Synthesize modifier keys events on pointer events --- .../linux/fl_key_embedder_responder.cc | 17 +++++++ .../linux/fl_key_embedder_responder.h | 14 ++++++ shell/platform/linux/fl_keyboard_manager.cc | 11 +++++ shell/platform/linux/fl_keyboard_manager.h | 13 ++++++ .../linux/fl_keyboard_manager_test.cc | 46 +++++++++++++++++++ shell/platform/linux/fl_view.cc | 7 ++- 6 files changed, 107 insertions(+), 1 deletion(-) diff --git a/shell/platform/linux/fl_key_embedder_responder.cc b/shell/platform/linux/fl_key_embedder_responder.cc index 44fa1b36ac490..6c9c91f102a0d 100644 --- a/shell/platform/linux/fl_key_embedder_responder.cc +++ b/shell/platform/linux/fl_key_embedder_responder.cc @@ -868,3 +868,20 @@ static void fl_key_embedder_responder_handle_event( self->send_key_event(&kEmptyEvent, nullptr, nullptr); } } + +void fl_key_embedder_responder_sync_modifiers_if_needed( + FlKeyEmbedderResponder* responder, + guint state, + double event_time) { + const double timestamp = event_time * kMicrosecondsPerMillisecond; + + SyncStateLoopContext sync_state_context; + sync_state_context.self = responder; + sync_state_context.state = state; + sync_state_context.timestamp = timestamp; + + // Update pressing states. + g_hash_table_foreach(responder->modifier_bit_to_checked_keys, + synchronize_pressed_states_loop_body, + &sync_state_context); +} diff --git a/shell/platform/linux/fl_key_embedder_responder.h b/shell/platform/linux/fl_key_embedder_responder.h index 0eb0659435bd5..d7b3af8c0b410 100644 --- a/shell/platform/linux/fl_key_embedder_responder.h +++ b/shell/platform/linux/fl_key_embedder_responder.h @@ -50,6 +50,20 @@ G_DECLARE_FINAL_TYPE(FlKeyEmbedderResponder, FlKeyEmbedderResponder* fl_key_embedder_responder_new( EmbedderSendKeyEvent send_key_event); +/** + * fl_key_embedder_responder_sync_modifiers_if_needed: + * @responder: the #FlKeyEmbedderResponder self. + * @state: the state of the modifiers mask. + * @event_time: the time attribute of the incoming GDK event. + * + * If needed, synthesize modifier keys up and down event by comparing their + * current pressing states with the given modifiers mask. + */ +void fl_key_embedder_responder_sync_modifiers_if_needed( + FlKeyEmbedderResponder* responder, + guint state, + double event_time); + 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 06762cee6294d..fc3c8ca31b38a 100644 --- a/shell/platform/linux/fl_keyboard_manager.cc +++ b/shell/platform/linux/fl_keyboard_manager.cc @@ -610,3 +610,14 @@ gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* self) { return self->pending_responds->len == 0 && self->pending_redispatches->len == 0; } + +void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* self, + guint state, + double event_time) { + // The embedder responder is the first element in + // FlKeyboardManager.responder_list. + FlKeyEmbedderResponder* responder = + FL_KEY_EMBEDDER_RESPONDER(g_ptr_array_index(self->responder_list, 0)); + fl_key_embedder_responder_sync_modifiers_if_needed(responder, state, + event_time); +} diff --git a/shell/platform/linux/fl_keyboard_manager.h b/shell/platform/linux/fl_keyboard_manager.h index fb5d35d9bce52..688e49f972a92 100644 --- a/shell/platform/linux/fl_keyboard_manager.h +++ b/shell/platform/linux/fl_keyboard_manager.h @@ -70,6 +70,19 @@ gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* manager, */ gboolean fl_keyboard_manager_is_state_clear(FlKeyboardManager* manager); +/** + * fl_keyboard_manager_sync_modifier_if_needed: + * @manager: the #FlKeyboardManager self. + * @state: the state of the modifiers mask. + * @event_time: the time attribute of the incoming GDK event. + * + * If needed, synthesize modifier keys up and down event by comparing their + * current pressing states with the given modifiers mask. + */ +void fl_keyboard_manager_sync_modifier_if_needed(FlKeyboardManager* manager, + guint state, + double event_time); + 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 b9856cedfe506..b408cb89eac00 100644 --- a/shell/platform/linux/fl_keyboard_manager_test.cc +++ b/shell/platform/linux/fl_keyboard_manager_test.cc @@ -32,20 +32,28 @@ call_records.clear() namespace { +using ::flutter::testing::keycodes::kLogicalAltLeft; using ::flutter::testing::keycodes::kLogicalBracketLeft; using ::flutter::testing::keycodes::kLogicalComma; +using ::flutter::testing::keycodes::kLogicalControlLeft; using ::flutter::testing::keycodes::kLogicalDigit1; using ::flutter::testing::keycodes::kLogicalKeyA; using ::flutter::testing::keycodes::kLogicalKeyB; using ::flutter::testing::keycodes::kLogicalKeyM; using ::flutter::testing::keycodes::kLogicalKeyQ; +using ::flutter::testing::keycodes::kLogicalMetaLeft; using ::flutter::testing::keycodes::kLogicalMinus; using ::flutter::testing::keycodes::kLogicalParenthesisRight; using ::flutter::testing::keycodes::kLogicalSemicolon; +using ::flutter::testing::keycodes::kLogicalShiftLeft; using ::flutter::testing::keycodes::kLogicalUnderscore; +using ::flutter::testing::keycodes::kPhysicalAltLeft; +using ::flutter::testing::keycodes::kPhysicalControlLeft; using ::flutter::testing::keycodes::kPhysicalKeyA; using ::flutter::testing::keycodes::kPhysicalKeyB; +using ::flutter::testing::keycodes::kPhysicalMetaLeft; +using ::flutter::testing::keycodes::kPhysicalShiftLeft; // Hardware key codes. typedef std::function AsyncKeyCallback; @@ -880,6 +888,44 @@ TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) { VERIFY_DOWN(kLogicalBracketLeft, "["); } +TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) { + KeyboardTester tester; + std::vector call_records; + tester.recordEmbedderCallsTo(call_records); + + auto verifyModifierIsSynthesized = [&](GdkModifierType mask, + uint64_t physical, uint64_t logical) { + // Modifier is pressed. + guint state = mask; + fl_keyboard_manager_sync_modifier_if_needed(tester.manager(), state, 1000); + EXPECT_EQ(call_records.size(), 1u); + EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, physical, + logical, NULL, true); + // Modifier is released. + state = state ^ mask; + fl_keyboard_manager_sync_modifier_if_needed(tester.manager(), state, 1001); + EXPECT_EQ(call_records.size(), 2u); + EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeUp, physical, logical, + NULL, true); + call_records.clear(); + }; + + // No modifiers pressed. + guint state = 0; + fl_keyboard_manager_sync_modifier_if_needed(tester.manager(), state, 1000); + EXPECT_EQ(call_records.size(), 0u); + call_records.clear(); + + // Press and release each modifier once. + verifyModifierIsSynthesized(GDK_CONTROL_MASK, kPhysicalControlLeft, + kLogicalControlLeft); + verifyModifierIsSynthesized(GDK_META_MASK, kPhysicalMetaLeft, + kLogicalMetaLeft); + verifyModifierIsSynthesized(GDK_MOD1_MASK, kPhysicalAltLeft, kLogicalAltLeft); + verifyModifierIsSynthesized(GDK_SHIFT_MASK, kPhysicalShiftLeft, + kLogicalShiftLeft); +} + // The following layout data is generated using DEBUG_PRINT_LAYOUT. const MockGroupLayoutData kLayoutUs0{{ diff --git a/shell/platform/linux/fl_view.cc b/shell/platform/linux/fl_view.cc index 967dd67d7cbce..69fea7ccafe6f 100644 --- a/shell/platform/linux/fl_view.cc +++ b/shell/platform/linux/fl_view.cc @@ -164,6 +164,8 @@ static gboolean send_pointer_button_event(FlView* self, GdkEventButton* event) { fl_scrolling_manager_set_last_mouse_position(self->scrolling_manager, event->x * scale_factor, event->y * scale_factor); + fl_keyboard_manager_sync_modifier_if_needed(self->keyboard_manager, + event->state, event->time); fl_engine_send_mouse_pointer_event( self->engine, phase, event->time * kMicrosecondsPerMillisecond, event->x * scale_factor, event->y * scale_factor, 0, 0, @@ -172,7 +174,7 @@ static gboolean send_pointer_button_event(FlView* self, GdkEventButton* event) { return TRUE; } -// Geneartes a mouse pointer event if the pointer appears inside the window. +// Generates a mouse pointer event if the pointer appears inside the window. static void check_pointer_inside(FlView* view, GdkEvent* event) { if (!view->pointer_inside) { view->pointer_inside = TRUE; @@ -402,6 +404,9 @@ static gboolean motion_notify_event_cb(GtkWidget* widget, check_pointer_inside(view, reinterpret_cast(event)); gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(view)); + + fl_keyboard_manager_sync_modifier_if_needed(view->keyboard_manager, + event->state, event->time); fl_engine_send_mouse_pointer_event( view->engine, view->button_state != 0 ? kMove : kHover, event->time * kMicrosecondsPerMillisecond, event->x * scale_factor,