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

[Linux] Synthesize modifier keys events on pointer events #37491

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions shell/platform/linux/fl_key_embedder_responder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
14 changes: 14 additions & 0 deletions shell/platform/linux/fl_key_embedder_responder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_
11 changes: 11 additions & 0 deletions shell/platform/linux/fl_keyboard_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
13 changes: 13 additions & 0 deletions shell/platform/linux/fl_keyboard_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_
46 changes: 46 additions & 0 deletions shell/platform/linux/fl_keyboard_manager_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<void(bool handled)> AsyncKeyCallback;
Expand Down Expand Up @@ -880,6 +888,44 @@ TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) {
VERIFY_DOWN(kLogicalBracketLeft, "[");
}

TEST(FlKeyboardManagerTest, SynthesizeModifiersIfNeeded) {
KeyboardTester tester;
std::vector<CallRecord> call_records;
tester.recordEmbedderCallsTo(call_records);

auto verifyModifierIsSynthesized = [&](GdkModifierType mask,
uint64_t physical, uint64_t logical) {
// Modifier is pressed.
guint state = mask;
fl_keyboard_manager_sync_modifier_if_needed(tester.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{{
Expand Down
7 changes: 6 additions & 1 deletion shell/platform/linux/fl_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -402,6 +404,9 @@ static gboolean motion_notify_event_cb(GtkWidget* widget,
check_pointer_inside(view, reinterpret_cast<GdkEvent*>(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,
Expand Down