Skip to content

Commit

Permalink
[Linux] Synthesize modifier keys events on pointer events
Browse files Browse the repository at this point in the history
  • Loading branch information
bleroux committed Nov 10, 2022
1 parent a71c0c6 commit dce3fe6
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 1 deletion.
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

0 comments on commit dce3fe6

Please sign in to comment.