| // Copyright 2013 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "flutter/shell/platform/linux/fl_key_embedder_responder.h" |
| |
| #include "gtest/gtest.h" |
| |
| #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" |
| #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h" |
| #include "flutter/shell/platform/linux/fl_binary_messenger_private.h" |
| #include "flutter/shell/platform/linux/fl_engine_private.h" |
| #include "flutter/shell/platform/linux/testing/fl_test.h" |
| |
| namespace { |
| constexpr gboolean kRelease = FALSE; |
| constexpr gboolean kPress = TRUE; |
| |
| constexpr gboolean kIsModifier = TRUE; |
| constexpr gboolean kIsNotModifier = FALSE; |
| |
| constexpr guint16 kKeyCodeDigit1 = 0x0au; |
| constexpr guint16 kKeyCodeKeyA = 0x26u; |
| constexpr guint16 kKeyCodeShiftLeft = 0x32u; |
| constexpr guint16 kKeyCodeShiftRight = 0x3Eu; |
| constexpr guint16 kKeyCodeAltRight = 0x6Cu; |
| constexpr guint16 kKeyCodeNumpad1 = 0x57u; |
| constexpr guint16 kKeyCodeNumLock = 0x4Du; |
| constexpr guint16 kKeyCodeCapsLock = 0x42u; |
| constexpr guint16 kKeyCodeControlLeft = 0x25u; |
| constexpr guint16 kKeyCodeControlRight = 0x69u; |
| |
| using namespace ::flutter::testing::keycodes; |
| } // namespace |
| |
| static void g_ptr_array_clear(GPtrArray* array) { |
| g_ptr_array_remove_range(array, 0, array->len); |
| } |
| |
| G_DECLARE_FINAL_TYPE(FlKeyEmbedderCallRecord, |
| fl_key_embedder_call_record, |
| FL, |
| KEY_EMBEDDER_CALL_RECORD, |
| GObject); |
| |
| struct _FlKeyEmbedderCallRecord { |
| GObject parent_instance; |
| |
| FlutterKeyEvent* event; |
| FlutterKeyEventCallback callback; |
| gpointer user_data; |
| }; |
| |
| G_DEFINE_TYPE(FlKeyEmbedderCallRecord, |
| fl_key_embedder_call_record, |
| G_TYPE_OBJECT) |
| |
| static void fl_key_embedder_call_record_init(FlKeyEmbedderCallRecord* self) {} |
| |
| // Dispose method for FlKeyEmbedderCallRecord. |
| static void fl_key_embedder_call_record_dispose(GObject* object) { |
| g_return_if_fail(FL_IS_KEY_EMBEDDER_CALL_RECORD(object)); |
| |
| FlKeyEmbedderCallRecord* self = FL_KEY_EMBEDDER_CALL_RECORD(object); |
| if (self->event != nullptr) { |
| g_free(const_cast<char*>(self->event->character)); |
| g_free(self->event); |
| } |
| G_OBJECT_CLASS(fl_key_embedder_call_record_parent_class)->dispose(object); |
| } |
| |
| // Class Initialization method for FlKeyEmbedderCallRecord class. |
| static void fl_key_embedder_call_record_class_init( |
| FlKeyEmbedderCallRecordClass* klass) { |
| G_OBJECT_CLASS(klass)->dispose = fl_key_embedder_call_record_dispose; |
| } |
| |
| static FlKeyEmbedderCallRecord* fl_key_embedder_call_record_new( |
| const FlutterKeyEvent* event, |
| FlutterKeyEventCallback callback, |
| gpointer user_data) { |
| g_return_val_if_fail(event != nullptr, nullptr); |
| |
| FlKeyEmbedderCallRecord* self = FL_KEY_EMBEDDER_CALL_RECORD( |
| g_object_new(fl_key_embedder_call_record_get_type(), nullptr)); |
| |
| FlutterKeyEvent* clone_event = g_new(FlutterKeyEvent, 1); |
| *clone_event = *event; |
| if (event->character != nullptr) { |
| size_t character_length = strlen(event->character); |
| char* clone_character = g_new(char, character_length + 1); |
| strncpy(clone_character, event->character, character_length + 1); |
| clone_event->character = clone_character; |
| } |
| self->event = clone_event; |
| self->callback = callback; |
| self->user_data = user_data; |
| |
| return self; |
| } |
| |
| namespace { |
| // A global variable to store new event. It is a global variable so that it can |
| // be returned by #fl_key_event_new_by_mock for easy use. |
| FlKeyEvent _g_key_event; |
| } // namespace |
| |
| // Create a new #FlKeyEvent with the given information. |
| // |
| // This event is passed to #fl_key_responder_handle_event, |
| // which assumes that the event is managed by callee. |
| // Therefore #fl_key_event_new_by_mock doesn't need to |
| // dynamically allocate, but reuses the same global object. |
| static FlKeyEvent* fl_key_event_new_by_mock(guint32 time_in_milliseconds, |
| bool is_press, |
| guint keyval, |
| guint16 keycode, |
| int state, |
| gboolean is_modifier) { |
| _g_key_event.is_press = is_press; |
| _g_key_event.time = time_in_milliseconds; |
| _g_key_event.state = state; |
| _g_key_event.keyval = keyval; |
| _g_key_event.string = nullptr; |
| _g_key_event.keycode = keycode; |
| _g_key_event.origin = nullptr; |
| _g_key_event.dispose_origin = nullptr; |
| return &_g_key_event; |
| } |
| |
| static gboolean g_expected_handled; |
| static gpointer g_expected_user_data; |
| |
| static void verify_response_handled(bool handled, gpointer user_data) { |
| EXPECT_EQ(handled, g_expected_handled); |
| } |
| |
| static void invoke_record_callback_and_verify(FlKeyEmbedderCallRecord* record, |
| bool expected_handled, |
| void* expected_user_data) { |
| g_return_if_fail(record->callback != nullptr); |
| g_expected_handled = expected_handled; |
| g_expected_user_data = expected_user_data; |
| record->callback(expected_handled, record->user_data); |
| } |
| |
| namespace { |
| GPtrArray* g_call_records; |
| } |
| |
| static EmbedderSendKeyEvent record_calls_in(GPtrArray* records_array) { |
| return [records_array](const FlutterKeyEvent* event, |
| FlutterKeyEventCallback callback, void* user_data) { |
| if (records_array != nullptr) { |
| g_ptr_array_add(records_array, fl_key_embedder_call_record_new( |
| event, callback, user_data)); |
| } |
| }; |
| } |
| |
| static void clear_g_call_records() { |
| g_ptr_array_free(g_call_records, TRUE); |
| g_call_records = nullptr; |
| } |
| |
| // Basic key presses |
| TEST(FlKeyEmbedderResponderTest, SendKeyEvent) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // On a QWERTY keyboard, press key Q (physically key A), and release. |
| // Key down |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(12345, kPress, GDK_KEY_a, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent)); |
| EXPECT_EQ(record->event->timestamp, 12345000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "a"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Key up |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(12346, kRelease, GDK_KEY_a, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent)); |
| EXPECT_EQ(record->event->timestamp, 12346000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, FALSE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // On an AZERTY keyboard, press key Q (physically key A), and release. |
| // Key down |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(12347, kPress, GDK_KEY_q, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent)); |
| EXPECT_EQ(record->event->timestamp, 12347000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyQ); |
| EXPECT_STREQ(record->event->character, "q"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Key up |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(12348, kRelease, GDK_KEY_q, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent)); |
| EXPECT_EQ(record->event->timestamp, 12348000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyQ); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, FALSE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Basic key presses, but uses the specified logical key if it is not 0. |
| TEST(FlKeyEmbedderResponderTest, UsesSpecifiedLogicalKey) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // On an AZERTY keyboard, press physical key 1, and release. |
| // Key down |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(12345, kPress, GDK_KEY_ampersand, kKeyCodeDigit1, |
| 0, kIsNotModifier), |
| verify_response_handled, &user_data, kLogicalDigit1); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent)); |
| EXPECT_EQ(record->event->timestamp, 12345000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalDigit1); |
| EXPECT_EQ(record->event->logical, kLogicalDigit1); |
| EXPECT_STREQ(record->event->character, "&"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Press Shift, key A, then release Shift, key A. |
| TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Press shift right |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_Shift_R, kKeyCodeShiftRight, |
| 0, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftRight); |
| EXPECT_EQ(record->event->logical, kLogicalShiftRight); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press key A |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA, 0x1, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "A"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release shift right |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Shift_R, |
| kKeyCodeShiftRight, 0x1, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftRight); |
| EXPECT_EQ(record->event->logical, kLogicalShiftRight); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release key A |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_A, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Press or release Numpad 1 between presses/releases of NumLock. |
| // |
| // This tests interaction between lock keys and non-lock keys in cases that do |
| // not have events missed. |
| // |
| // This also tests the result of Numpad keys across NumLock taps, which is |
| // test-worthy because the keyval for the numpad key will change before and |
| // after the NumLock tap, which should not alter the resulting logical key. |
| TEST(FlKeyEmbedderResponderTest, TapNumPadKeysBetweenNumLockEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Press Numpad 1 (stage 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_KP_End, kKeyCodeNumpad1, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumpad1); |
| EXPECT_EQ(record->event->logical, kLogicalNumpad1); |
| EXPECT_STREQ(record->event->character, nullptr); // TODO |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press NumLock (stage 0 -> 1) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| 0, kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release numpad 1 (stage 1) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_KP_1, kKeyCodeNumpad1, |
| 0x10, kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumpad1); |
| EXPECT_EQ(record->event->logical, kLogicalNumpad1); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release NumLock (stage 1 -> 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| 0x10, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press Numpad 1 (stage 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_KP_End, kKeyCodeNumpad1, |
| 0x10, kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumpad1); |
| EXPECT_EQ(record->event->logical, kLogicalNumpad1); |
| EXPECT_STREQ(record->event->character, nullptr); // TODO |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press NumLock (stage 2 -> 3) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| 0x10, kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release numpad 1 (stage 3) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_KP_1, kKeyCodeNumpad1, |
| 0x10, kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumpad1); |
| EXPECT_EQ(record->event->logical, kLogicalNumpad1); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release NumLock (stage 3 -> 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| 0x10, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Press or release digit 1 between presses/releases of Shift. |
| // |
| // GTK will change the virtual key during a key tap, and the embedder |
| // should regularize it. |
| TEST(FlKeyEmbedderResponderTest, ReleaseShiftKeyBetweenDigitKeyEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| guint state = 0; |
| |
| // Press shift left |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_Shift_L, kKeyCodeShiftLeft, |
| state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftLeft); |
| EXPECT_EQ(record->event->logical, kLogicalShiftLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| state = GDK_SHIFT_MASK; |
| |
| // Press digit 1, which is '!' on a US keyboard |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_exclam, kKeyCodeDigit1, |
| state, kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalDigit1); |
| EXPECT_EQ(record->event->logical, kLogicalExclamation); |
| EXPECT_STREQ(record->event->character, "!"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release shift |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Shift_L, |
| kKeyCodeShiftLeft, state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftLeft); |
| EXPECT_EQ(record->event->logical, kLogicalShiftLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| state = 0; |
| |
| // Release digit 1, which is "1" because shift has been released. |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_1, kKeyCodeDigit1, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalDigit1); |
| EXPECT_EQ(record->event->logical, kLogicalExclamation); // Important |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Press or release letter key between presses/releases of CapsLock. |
| // |
| // This tests interaction between lock keys and non-lock keys in cases that do |
| // not have events missed. |
| TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Press CapsLock (stage 0 -> 1) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock, |
| 0x0, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press key A (stage 1) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA, 0x2, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "A"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release CapsLock (stage 1 -> 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Caps_Lock, |
| kKeyCodeCapsLock, 0x2, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release key A (stage 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_A, kKeyCodeKeyA, 0x2, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press CapsLock (stage 2 -> 3) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(105, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock, |
| 0x2, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press key A (stage 3) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(106, kPress, GDK_KEY_A, kKeyCodeKeyA, 0x2, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "A"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release CapsLock (stage 3 -> 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(107, kRelease, GDK_KEY_Caps_Lock, |
| kKeyCodeCapsLock, 0x2, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release key A (stage 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(108, kRelease, GDK_KEY_a, kKeyCodeKeyA, 0x0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Press or release letter key between presses/releases of CapsLock, on |
| // a platform with reversed logic. |
| // |
| // This happens when using a Chrome remote desktop on MacOS. |
| TEST(FlKeyEmbedderResponderTest, TapLetterKeysBetweenCapsLockEventsReversed) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Press key A (stage 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA, 0x0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "a"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press CapsLock (stage 0 -> 1) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock, |
| 0x2, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release CapsLock (stage 1 -> 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Caps_Lock, |
| kKeyCodeCapsLock, 0x2, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release key A (stage 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_A, kKeyCodeKeyA, 0x2, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press key A (stage 2) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(105, kPress, GDK_KEY_A, kKeyCodeKeyA, 0x2, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "A"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Press CapsLock (stage 2 -> 3) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(106, kPress, GDK_KEY_Caps_Lock, kKeyCodeCapsLock, |
| 0x0, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release CapsLock (stage 3 -> 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(107, kRelease, GDK_KEY_Caps_Lock, |
| kKeyCodeCapsLock, 0x2, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalCapsLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release key A (stage 0) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(108, kRelease, GDK_KEY_a, kKeyCodeKeyA, 0x0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| TEST(FlKeyEmbedderResponderTest, TurnDuplicateDownEventsToRepeats) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Press KeyA |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Another KeyA down events, which usually means a repeated event. |
| g_expected_handled = false; |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_a, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeRepeat); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "a"); |
| EXPECT_EQ(record->event->synthesized, false); |
| EXPECT_NE(record->callback, nullptr); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release KeyA |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_q, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| TEST(FlKeyEmbedderResponderTest, IgnoreAbruptUpEvent) { |
| FlKeyEmbedderCallRecord* record; |
| |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| // Release KeyA before it was even pressed. |
| g_expected_handled = true; // The empty event is always handled. |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_q, kKeyCodeKeyA, 0, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->physical, 0ull); |
| EXPECT_EQ(record->event->logical, 0ull); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| EXPECT_EQ(record->callback, nullptr); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Test if missed modifier keys can be detected and synthesized with state |
| // information upon events that are for this modifier key. |
| TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncPressingStateOnSelfEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Test 1: synthesize key down. |
| |
| // A key down of control left is missed. |
| guint state = GDK_CONTROL_MASK; |
| |
| // Send a ControlLeft up |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kRelease, GDK_KEY_Control_L, |
| kKeyCodeControlLeft, state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Test 2: synthesize key up. |
| |
| // Send a ControlLeft down. |
| state = 0; |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_Control_L, |
| kKeyCodeControlLeft, state, kIsModifier), |
| verify_response_handled, &user_data); |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // A key up of control left is missed. |
| state = 0; |
| |
| // Send another ControlLeft down |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kPress, GDK_KEY_Control_L, |
| kKeyCodeControlLeft, state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 103000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 103000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Send a ControlLeft up to clear up state. |
| state = GDK_CONTROL_MASK; |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(104, kRelease, GDK_KEY_Control_L, |
| kKeyCodeControlLeft, state, kIsModifier), |
| verify_response_handled, &user_data); |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Test 3: synthesize by right modifier. |
| |
| // A key down of control right is missed. |
| state = GDK_CONTROL_MASK; |
| |
| // Send a ControlRight up. |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(105, kRelease, GDK_KEY_Control_R, |
| kKeyCodeControlRight, state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| // A ControlLeft down is synthesized, with an empty event. |
| // Reason: The ControlLeft down is synthesized to synchronize the state |
| // showing Control as pressed. The ControlRight event is ignored because |
| // the event is considered a duplicate up event. |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 105000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Test if missed modifier keys can be detected and synthesized with state |
| // information upon events that are not for this modifier key. |
| TEST(FlKeyEmbedderResponderTest, |
| SynthesizeForDesyncPressingStateOnNonSelfEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // A key down of control left is missed. |
| guint state = GDK_CONTROL_MASK; |
| |
| // Send a normal event (KeyA down) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "a"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // A key up of control left is missed. |
| state = 0; |
| |
| // Send a normal event (KeyA up) |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kRelease, GDK_KEY_A, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Test non-default key mapping. |
| |
| // Press a key with physical CapsLock and logical ControlLeft. |
| state = 0; |
| |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_Control_L, kKeyCodeCapsLock, |
| state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // The key up of the control left press is missed. |
| state = 0; |
| |
| // Send a normal event (KeyA down). |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| // The synthesized event should have physical CapsLock and logical |
| // ControlLeft. |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "A"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Test if missed modifier keys can be detected and synthesized with state |
| // information upon events that do not have the standard key mapping. |
| TEST(FlKeyEmbedderResponderTest, |
| SynthesizeForDesyncPressingStateOnRemappedEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // Press a key with physical CapsLock and logical ControlLeft. |
| guint state = 0; |
| |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_Control_L, kKeyCodeCapsLock, |
| state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // The key up of the control left press is missed. |
| state = 0; |
| |
| // Send a normal event (KeyA down). |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_A, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| // The synthesized event should have physical CapsLock and logical |
| // ControlLeft. |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalCapsLock); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "A"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Test if missed lock keys can be detected and synthesized with state |
| // information upon events that are not for this modifier key. |
| TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnNonSelfEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // The NumLock is desynchronized by being enabled. |
| guint state = GDK_MOD2_MASK; |
| |
| // Send a normal event |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_a, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, "a"); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // The NumLock is desynchronized by being disabled. |
| state = 0; |
| |
| // Release key A |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kRelease, GDK_KEY_A, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 4u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 2)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 3)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalKeyA); |
| EXPECT_EQ(record->event->logical, kLogicalKeyA); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // Release NumLock. Since the previous event should have synthesized NumLock |
| // to be released, this should result in only an empty event. |
| g_expected_handled = true; |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(103, kRelease, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->physical, 0ull); |
| EXPECT_EQ(record->event->logical, 0ull); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| EXPECT_EQ(record->callback, nullptr); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Test if missed lock keys can be detected and synthesized with state |
| // information upon events that are for this modifier key. |
| TEST(FlKeyEmbedderResponderTest, SynthesizeForDesyncLockModeOnSelfEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // The NumLock is desynchronized by being enabled. |
| guint state = GDK_MOD2_MASK; |
| |
| // NumLock down |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 3u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 2)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| // The NumLock is desynchronized by being enabled in a press event. |
| state = GDK_MOD2_MASK; |
| |
| // NumLock up |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(102, kPress, GDK_KEY_Num_Lock, kKeyCodeNumLock, |
| state, kIsModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 4u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 2)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 3)); |
| EXPECT_EQ(record->event->timestamp, 102000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| invoke_record_callback_and_verify(record, TRUE, &user_data); |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // Ensures that even if the primary event is ignored (due to duplicate |
| // key up or down events), key synthesization is still performed. |
| TEST(FlKeyEmbedderResponderTest, SynthesizationOccursOnIgnoredEvents) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| int user_data = 123; // Arbitrary user data |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| // The NumLock is desynchronized by being enabled, and Control is pressed. |
| guint state = GDK_MOD2_MASK | GDK_CONTROL_MASK; |
| |
| // Send a KeyA up event, which will be ignored. |
| g_expected_handled = true; // The ignored event is always handled. |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(101, kRelease, GDK_KEY_a, kKeyCodeKeyA, state, |
| kIsNotModifier), |
| verify_response_handled, &user_data); |
| |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalNumLock); |
| EXPECT_EQ(record->event->logical, kLogicalNumLock); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->timestamp, 101000); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalControlLeft); |
| EXPECT_EQ(record->event->logical, kLogicalControlLeft); |
| EXPECT_STREQ(record->event->character, nullptr); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| g_ptr_array_clear(g_call_records); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |
| |
| // This test case occurs when the following two cases collide: |
| // |
| // 1. When holding shift, AltRight key gives logical GDK_KEY_Meta_R with the |
| // state bitmask still MOD3 (Alt). |
| // 2. When holding AltRight, ShiftLeft key gives logical GDK_KEY_ISO_Next_Group |
| // with the state bitmask RESERVED_14. |
| // |
| // The resulting event sequence is not perfectly ideal: it had to synthesize |
| // AltLeft down because the physical AltRight key corresponds to logical |
| // MetaRight at the moment. |
| TEST(FlKeyEmbedderResponderTest, HandlesShiftAltVersusGroupNext) { |
| EXPECT_EQ(g_call_records, nullptr); |
| g_call_records = g_ptr_array_new_with_free_func(g_object_unref); |
| FlKeyResponder* responder = FL_KEY_RESPONDER( |
| fl_key_embedder_responder_new(record_calls_in(g_call_records))); |
| |
| g_expected_handled = true; |
| guint32 now_time = 1; |
| // A convenient shorthand to simulate events. |
| auto send_key_event = [responder, &now_time](bool is_press, guint keyval, |
| guint16 keycode, int state) { |
| now_time += 1; |
| int user_data = 123; // Arbitrary user data |
| fl_key_responder_handle_event( |
| responder, |
| fl_key_event_new_by_mock(now_time, is_press, keyval, keycode, state, |
| kIsModifier), |
| verify_response_handled, &user_data); |
| }; |
| |
| FlKeyEmbedderCallRecord* record; |
| |
| send_key_event(kPress, GDK_KEY_Shift_L, kKeyCodeShiftLeft, 0x2000000); |
| EXPECT_EQ(g_call_records->len, 1u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftLeft); |
| EXPECT_EQ(record->event->logical, kLogicalShiftLeft); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| send_key_event(kPress, GDK_KEY_Meta_R, kKeyCodeAltRight, 0x2000001); |
| EXPECT_EQ(g_call_records->len, 2u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 1)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalAltRight); |
| EXPECT_EQ(record->event->logical, kLogicalMetaRight); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| send_key_event(kRelease, GDK_KEY_ISO_Next_Group, kKeyCodeShiftLeft, |
| 0x2000009); |
| EXPECT_EQ(g_call_records->len, 5u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 2)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalAltLeft); |
| EXPECT_EQ(record->event->logical, kLogicalAltLeft); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 3)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalAltRight); |
| EXPECT_EQ(record->event->logical, kLogicalMetaRight); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 4)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftLeft); |
| EXPECT_EQ(record->event->logical, kLogicalShiftLeft); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| send_key_event(kPress, GDK_KEY_ISO_Next_Group, kKeyCodeShiftLeft, 0x2000008); |
| EXPECT_EQ(g_call_records->len, 6u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 5)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftLeft); |
| EXPECT_EQ(record->event->logical, kLogicalGroupNext); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| send_key_event(kRelease, GDK_KEY_ISO_Level3_Shift, kKeyCodeAltRight, |
| 0x2002008); |
| EXPECT_EQ(g_call_records->len, 7u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 6)); |
| EXPECT_EQ(record->event->physical, 0u); |
| EXPECT_EQ(record->event->logical, 0u); |
| |
| send_key_event(kRelease, GDK_KEY_Shift_L, kKeyCodeShiftLeft, 0x2002000); |
| EXPECT_EQ(g_call_records->len, 9u); |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 7)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalAltLeft); |
| EXPECT_EQ(record->event->logical, kLogicalAltLeft); |
| EXPECT_EQ(record->event->synthesized, true); |
| |
| record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 8)); |
| EXPECT_EQ(record->event->type, kFlutterKeyEventTypeUp); |
| EXPECT_EQ(record->event->physical, kPhysicalShiftLeft); |
| EXPECT_EQ(record->event->logical, kLogicalGroupNext); |
| EXPECT_EQ(record->event->synthesized, false); |
| |
| clear_g_call_records(); |
| g_object_unref(responder); |
| } |