| // 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_keyboard_manager.h" |
| |
| #include <cstring> |
| #include <vector> |
| |
| #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h" |
| #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h" |
| #include "flutter/shell/platform/linux/testing/mock_text_input_plugin.h" |
| #include "gtest/gtest.h" |
| |
| // Define compound `expect` in macros. If they were defined in functions, the |
| // stacktrace wouldn't print where the function is called in the unit tests. |
| |
| #define EXPECT_KEY_EVENT(RECORD, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \ |
| EXPECT_EQ((RECORD).type, CallRecord::kKeyCallEmbedder); \ |
| EXPECT_EQ((RECORD).event->type, (TYPE)); \ |
| EXPECT_EQ((RECORD).event->physical, (PHYSICAL)); \ |
| EXPECT_EQ((RECORD).event->logical, (LOGICAL)); \ |
| EXPECT_STREQ((RECORD).event->character, (CHAR)); \ |
| EXPECT_EQ((RECORD).event->synthesized, (SYNTHESIZED)); |
| |
| #define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR) \ |
| EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); \ |
| EXPECT_EQ(call_records[0].event->type, kFlutterKeyEventTypeDown); \ |
| EXPECT_EQ(call_records[0].event->logical, (OUT_LOGICAL)); \ |
| EXPECT_STREQ(call_records[0].event->character, (OUT_CHAR)); \ |
| EXPECT_EQ(call_records[0].event->synthesized, false); \ |
| 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; |
| typedef std::function<void(AsyncKeyCallback callback)> ChannelCallHandler; |
| typedef std::function<void(const FlutterKeyEvent* event, |
| AsyncKeyCallback callback)> |
| EmbedderCallHandler; |
| typedef std::function<void(std::unique_ptr<FlKeyEvent>)> RedispatchHandler; |
| |
| // A type that can record all kinds of effects that the keyboard manager |
| // triggers. |
| // |
| // An instance of `CallRecord` might not have all the fields filled. |
| typedef struct { |
| enum { |
| kKeyCallEmbedder, |
| kKeyCallChannel, |
| } type; |
| |
| AsyncKeyCallback callback; |
| std::unique_ptr<FlutterKeyEvent> event; |
| std::unique_ptr<char[]> event_character; |
| } CallRecord; |
| |
| // Clone a C-string. |
| // |
| // Must be deleted by delete[]. |
| char* cloneString(const char* source) { |
| if (source == nullptr) { |
| return nullptr; |
| } |
| size_t charLen = strlen(source); |
| char* target = new char[charLen + 1]; |
| strncpy(target, source, charLen + 1); |
| return target; |
| } |
| |
| constexpr guint16 kKeyCodeKeyA = 0x26u; |
| constexpr guint16 kKeyCodeKeyB = 0x38u; |
| constexpr guint16 kKeyCodeKeyM = 0x3au; |
| constexpr guint16 kKeyCodeDigit1 = 0x0au; |
| constexpr guint16 kKeyCodeMinus = 0x14u; |
| constexpr guint16 kKeyCodeSemicolon = 0x2fu; |
| constexpr guint16 kKeyCodeKeyLeftBracket = 0x22u; |
| |
| static constexpr char kKeyEventChannelName[] = "flutter/keyevent"; |
| |
| // All key clues for a keyboard layout. |
| // |
| // The index is (keyCode * 2 + hasShift), where each value is the character for |
| // this key (GTK only supports UTF-16.) Since the maximum keycode of interest |
| // is 128, it has a total of 256 entries.. |
| typedef std::array<uint32_t, 256> MockGroupLayoutData; |
| typedef std::vector<const MockGroupLayoutData*> MockLayoutData; |
| |
| extern const MockLayoutData kLayoutUs; |
| extern const MockLayoutData kLayoutRussian; |
| extern const MockLayoutData kLayoutFrench; |
| |
| G_BEGIN_DECLS |
| |
| G_DECLARE_FINAL_TYPE(FlMockViewDelegate, |
| fl_mock_view_delegate, |
| FL, |
| MOCK_VIEW_DELEGATE, |
| GObject); |
| |
| G_DECLARE_FINAL_TYPE(FlMockKeyBinaryMessenger, |
| fl_mock_key_binary_messenger, |
| FL, |
| MOCK_KEY_BINARY_MESSENGER, |
| GObject) |
| |
| G_END_DECLS |
| |
| /***** FlMockKeyBinaryMessenger *****/ |
| /* Mock a binary messenger that only processes messages from the embedding on |
| * the key event channel, and does so according to the callback set by |
| * fl_mock_key_binary_messenger_set_callback_handler */ |
| |
| struct _FlMockKeyBinaryMessenger { |
| GObject parent_instance; |
| |
| ChannelCallHandler callback_handler; |
| }; |
| |
| static void fl_mock_key_binary_messenger_iface_init( |
| FlBinaryMessengerInterface* iface); |
| |
| G_DEFINE_TYPE_WITH_CODE( |
| FlMockKeyBinaryMessenger, |
| fl_mock_key_binary_messenger, |
| G_TYPE_OBJECT, |
| G_IMPLEMENT_INTERFACE(fl_binary_messenger_get_type(), |
| fl_mock_key_binary_messenger_iface_init)) |
| |
| static void fl_mock_key_binary_messenger_class_init( |
| FlMockKeyBinaryMessengerClass* klass) {} |
| |
| static void fl_mock_key_binary_messenger_send_on_channel( |
| FlBinaryMessenger* messenger, |
| const gchar* channel, |
| GBytes* message, |
| GCancellable* cancellable, |
| GAsyncReadyCallback callback, |
| gpointer user_data) { |
| FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER(messenger); |
| |
| if (callback != nullptr) { |
| EXPECT_STREQ(channel, kKeyEventChannelName); |
| self->callback_handler([self, cancellable, callback, |
| user_data](bool handled) { |
| g_autoptr(GTask) task = |
| g_task_new(self, cancellable, callback, user_data); |
| g_autoptr(FlValue) result = fl_value_new_map(); |
| fl_value_set_string_take(result, "handled", fl_value_new_bool(handled)); |
| g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new(); |
| g_autoptr(GError) error = nullptr; |
| GBytes* data = fl_message_codec_encode_message(FL_MESSAGE_CODEC(codec), |
| result, &error); |
| |
| g_task_return_pointer(task, data, |
| reinterpret_cast<GDestroyNotify>(g_bytes_unref)); |
| }); |
| } |
| } |
| |
| static GBytes* fl_mock_key_binary_messenger_send_on_channel_finish( |
| FlBinaryMessenger* messenger, |
| GAsyncResult* result, |
| GError** error) { |
| return static_cast<GBytes*>(g_task_propagate_pointer(G_TASK(result), error)); |
| } |
| |
| static void fl_mock_key_binary_messenger_iface_init( |
| FlBinaryMessengerInterface* iface) { |
| iface->set_message_handler_on_channel = |
| [](FlBinaryMessenger* messenger, const gchar* channel, |
| FlBinaryMessengerMessageHandler handler, gpointer user_data, |
| GDestroyNotify destroy_notify) { |
| EXPECT_STREQ(channel, kKeyEventChannelName); |
| // No need to mock. The key event channel expects no incoming messages |
| // from the framework. |
| }; |
| iface->send_response = [](FlBinaryMessenger* messenger, |
| FlBinaryMessengerResponseHandle* response_handle, |
| GBytes* response, GError** error) -> gboolean { |
| // The key event channel expects no incoming messages from the framework, |
| // hence no responses either. |
| g_return_val_if_reached(TRUE); |
| return TRUE; |
| }; |
| iface->send_on_channel = fl_mock_key_binary_messenger_send_on_channel; |
| iface->send_on_channel_finish = |
| fl_mock_key_binary_messenger_send_on_channel_finish; |
| } |
| |
| static void fl_mock_key_binary_messenger_init(FlMockKeyBinaryMessenger* self) {} |
| |
| static FlMockKeyBinaryMessenger* fl_mock_key_binary_messenger_new() { |
| FlMockKeyBinaryMessenger* self = FL_MOCK_KEY_BINARY_MESSENGER( |
| g_object_new(fl_mock_key_binary_messenger_get_type(), NULL)); |
| |
| // Added to stop compiler complaining about an unused function. |
| FL_IS_MOCK_KEY_BINARY_MESSENGER(self); |
| |
| return self; |
| } |
| |
| static void fl_mock_key_binary_messenger_set_callback_handler( |
| FlMockKeyBinaryMessenger* self, |
| ChannelCallHandler handler) { |
| self->callback_handler = std::move(handler); |
| } |
| |
| /***** FlMockViewDelegate *****/ |
| |
| struct _FlMockViewDelegate { |
| GObject parent_instance; |
| |
| FlMockKeyBinaryMessenger* messenger; |
| EmbedderCallHandler embedder_handler; |
| bool text_filter_result; |
| RedispatchHandler redispatch_handler; |
| KeyboardLayoutNotifier layout_notifier; |
| const MockLayoutData* layout_data; |
| }; |
| |
| static void fl_mock_view_keyboard_delegate_iface_init( |
| FlKeyboardViewDelegateInterface* iface); |
| |
| G_DEFINE_TYPE_WITH_CODE( |
| FlMockViewDelegate, |
| fl_mock_view_delegate, |
| G_TYPE_OBJECT, |
| G_IMPLEMENT_INTERFACE(fl_keyboard_view_delegate_get_type(), |
| fl_mock_view_keyboard_delegate_iface_init)) |
| |
| static void fl_mock_view_delegate_init(FlMockViewDelegate* self) {} |
| |
| static void fl_mock_view_delegate_dispose(GObject* object) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(object); |
| |
| g_clear_object(&self->messenger); |
| |
| G_OBJECT_CLASS(fl_mock_view_delegate_parent_class)->dispose(object); |
| } |
| |
| static void fl_mock_view_delegate_class_init(FlMockViewDelegateClass* klass) { |
| G_OBJECT_CLASS(klass)->dispose = fl_mock_view_delegate_dispose; |
| } |
| |
| static FlKeyEvent* fl_key_event_clone_information_only(FlKeyEvent* event); |
| |
| static void fl_mock_view_keyboard_send_key_event( |
| FlKeyboardViewDelegate* view_delegate, |
| const FlutterKeyEvent* event, |
| FlutterKeyEventCallback callback, |
| void* user_data) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); |
| self->embedder_handler(event, [callback, user_data](bool handled) { |
| if (callback != nullptr) { |
| callback(handled, user_data); |
| } |
| }); |
| } |
| |
| static gboolean fl_mock_view_keyboard_text_filter_key_press( |
| FlKeyboardViewDelegate* view_delegate, |
| FlKeyEvent* event) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); |
| return self->text_filter_result; |
| } |
| |
| static FlBinaryMessenger* fl_mock_view_keyboard_get_messenger( |
| FlKeyboardViewDelegate* view_delegate) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); |
| return FL_BINARY_MESSENGER(self->messenger); |
| } |
| |
| static void fl_mock_view_keyboard_redispatch_event( |
| FlKeyboardViewDelegate* view_delegate, |
| std::unique_ptr<FlKeyEvent> event) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(view_delegate); |
| if (self->redispatch_handler) { |
| self->redispatch_handler(std::move(event)); |
| } |
| } |
| |
| static void fl_mock_view_keyboard_subscribe_to_layout_change( |
| FlKeyboardViewDelegate* delegate, |
| KeyboardLayoutNotifier notifier) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(delegate); |
| self->layout_notifier = std::move(notifier); |
| } |
| |
| static guint fl_mock_view_keyboard_lookup_key(FlKeyboardViewDelegate* delegate, |
| const GdkKeymapKey* key) { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE(delegate); |
| guint8 group = static_cast<guint8>(key->group); |
| EXPECT_LT(group, self->layout_data->size()); |
| const MockGroupLayoutData* group_layout = (*self->layout_data)[group]; |
| EXPECT_TRUE(group_layout != nullptr); |
| EXPECT_TRUE(key->level == 0 || key->level == 1); |
| bool shift = key->level == 1; |
| return (*group_layout)[key->keycode * 2 + shift]; |
| } |
| |
| static void fl_mock_view_keyboard_delegate_iface_init( |
| FlKeyboardViewDelegateInterface* iface) { |
| iface->send_key_event = fl_mock_view_keyboard_send_key_event; |
| iface->text_filter_key_press = fl_mock_view_keyboard_text_filter_key_press; |
| iface->get_messenger = fl_mock_view_keyboard_get_messenger; |
| iface->redispatch_event = fl_mock_view_keyboard_redispatch_event; |
| iface->subscribe_to_layout_change = |
| fl_mock_view_keyboard_subscribe_to_layout_change; |
| iface->lookup_key = fl_mock_view_keyboard_lookup_key; |
| } |
| |
| static FlMockViewDelegate* fl_mock_view_delegate_new() { |
| FlMockViewDelegate* self = FL_MOCK_VIEW_DELEGATE( |
| g_object_new(fl_mock_view_delegate_get_type(), nullptr)); |
| |
| // Added to stop compiler complaining about an unused function. |
| FL_IS_MOCK_VIEW_DELEGATE(self); |
| |
| self->messenger = fl_mock_key_binary_messenger_new(); |
| |
| return self; |
| } |
| |
| static void fl_mock_view_set_embedder_handler(FlMockViewDelegate* self, |
| EmbedderCallHandler handler) { |
| self->embedder_handler = std::move(handler); |
| } |
| |
| static void fl_mock_view_set_text_filter_result(FlMockViewDelegate* self, |
| bool result) { |
| self->text_filter_result = result; |
| } |
| |
| static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self, |
| RedispatchHandler handler) { |
| self->redispatch_handler = std::move(handler); |
| } |
| |
| static void fl_mock_view_set_layout(FlMockViewDelegate* self, |
| const MockLayoutData* layout) { |
| self->layout_data = layout; |
| if (self->layout_notifier != nullptr) { |
| self->layout_notifier(); |
| } |
| } |
| |
| /***** End FlMockViewDelegate *****/ |
| |
| // Return a newly allocated #FlKeyEvent that is a clone to the given #event |
| // but with #origin and #dispose set to 0. |
| static FlKeyEvent* fl_key_event_clone_information_only(FlKeyEvent* event) { |
| FlKeyEvent* new_event = fl_key_event_clone(event); |
| new_event->origin = nullptr; |
| new_event->dispose_origin = nullptr; |
| return new_event; |
| } |
| |
| // Create a new #FlKeyEvent with the given information. |
| // |
| // The #origin will be another #FlKeyEvent with the exact information, |
| // so that it can be used to redispatch, and is freed upon disposal. |
| static FlKeyEvent* fl_key_event_new_by_mock(bool is_press, |
| guint keyval, |
| guint16 keycode, |
| int state, |
| gboolean is_modifier, |
| guint8 group = 0) { |
| FlKeyEvent* event = g_new(FlKeyEvent, 1); |
| event->is_press = is_press; |
| event->time = 0; |
| event->state = state; |
| event->keyval = keyval; |
| event->string = nullptr; |
| event->group = group; |
| event->keycode = keycode; |
| FlKeyEvent* origin_event = fl_key_event_clone_information_only(event); |
| event->origin = origin_event; |
| event->dispose_origin = [](gpointer origin) { g_free(origin); }; |
| return event; |
| } |
| |
| class KeyboardTester { |
| public: |
| KeyboardTester() { |
| view_ = fl_mock_view_delegate_new(); |
| respondToEmbedderCallsWith(false); |
| respondToChannelCallsWith(false); |
| respondToTextInputWith(false); |
| setLayout(kLayoutUs); |
| |
| manager_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_)); |
| } |
| |
| ~KeyboardTester() { |
| g_clear_object(&view_); |
| g_clear_object(&manager_); |
| } |
| |
| FlKeyboardManager* manager() { return manager_; } |
| |
| // Block until all GdkMainLoop messages are processed, which is basically |
| // used only for channel messages. |
| void flushChannelMessages() { |
| GMainLoop* loop = g_main_loop_new(nullptr, 0); |
| g_idle_add(_flushChannelMessagesCb, loop); |
| g_main_loop_run(loop); |
| } |
| |
| // Dispatch each of the given events, expect their results to be false |
| // (unhandled), and clear the event array. |
| // |
| // Returns the number of events redispatched. If any result is unexpected |
| // (handled), return a minus number `-x` instead, where `x` is the index of |
| // the first unexpected redispatch. |
| int redispatchEventsAndClear( |
| std::vector<std::unique_ptr<FlKeyEvent>>& events) { |
| size_t event_count = events.size(); |
| int first_error = -1; |
| during_redispatch_ = true; |
| for (size_t event_id = 0; event_id < event_count; event_id += 1) { |
| bool handled = fl_keyboard_manager_handle_event( |
| manager_, events[event_id].release()); |
| EXPECT_FALSE(handled); |
| if (handled) { |
| first_error = first_error == -1 ? event_id : first_error; |
| } |
| } |
| during_redispatch_ = false; |
| events.clear(); |
| return first_error < 0 ? event_count : -first_error; |
| } |
| |
| void respondToEmbedderCallsWith(bool response) { |
| fl_mock_view_set_embedder_handler( |
| view_, [response, this](const FlutterKeyEvent* event, |
| const AsyncKeyCallback& callback) { |
| EXPECT_FALSE(during_redispatch_); |
| callback(response); |
| }); |
| } |
| |
| void recordEmbedderCallsTo(std::vector<CallRecord>& storage) { |
| fl_mock_view_set_embedder_handler( |
| view_, [&storage, this](const FlutterKeyEvent* event, |
| AsyncKeyCallback callback) { |
| EXPECT_FALSE(during_redispatch_); |
| auto new_event = std::make_unique<FlutterKeyEvent>(*event); |
| char* new_event_character = cloneString(event->character); |
| new_event->character = new_event_character; |
| storage.push_back(CallRecord{ |
| .type = CallRecord::kKeyCallEmbedder, |
| .callback = std::move(callback), |
| .event = std::move(new_event), |
| .event_character = std::unique_ptr<char[]>(new_event_character), |
| }); |
| }); |
| } |
| |
| void respondToEmbedderCallsWithAndRecordsTo( |
| bool response, |
| std::vector<CallRecord>& storage) { |
| fl_mock_view_set_embedder_handler( |
| view_, [&storage, response, this](const FlutterKeyEvent* event, |
| const AsyncKeyCallback& callback) { |
| EXPECT_FALSE(during_redispatch_); |
| auto new_event = std::make_unique<FlutterKeyEvent>(*event); |
| char* new_event_character = cloneString(event->character); |
| new_event->character = new_event_character; |
| storage.push_back(CallRecord{ |
| .type = CallRecord::kKeyCallEmbedder, |
| .event = std::move(new_event), |
| .event_character = std::unique_ptr<char[]>(new_event_character), |
| }); |
| callback(response); |
| }); |
| } |
| |
| void respondToChannelCallsWith(bool response) { |
| fl_mock_key_binary_messenger_set_callback_handler( |
| view_->messenger, [response, this](const AsyncKeyCallback& callback) { |
| EXPECT_FALSE(during_redispatch_); |
| callback(response); |
| }); |
| } |
| |
| void recordChannelCallsTo(std::vector<CallRecord>& storage) { |
| fl_mock_key_binary_messenger_set_callback_handler( |
| view_->messenger, [&storage, this](AsyncKeyCallback callback) { |
| EXPECT_FALSE(during_redispatch_); |
| storage.push_back(CallRecord{ |
| .type = CallRecord::kKeyCallChannel, |
| .callback = std::move(callback), |
| }); |
| }); |
| } |
| |
| void respondToTextInputWith(bool response) { |
| fl_mock_view_set_text_filter_result(view_, response); |
| } |
| |
| void recordRedispatchedEventsTo( |
| std::vector<std::unique_ptr<FlKeyEvent>>& storage) { |
| fl_mock_view_set_redispatch_handler( |
| view_, [&storage](std::unique_ptr<FlKeyEvent> key) { |
| storage.push_back(std::move(key)); |
| }); |
| } |
| |
| void setLayout(const MockLayoutData& layout) { |
| fl_mock_view_set_layout(view_, &layout); |
| } |
| |
| private: |
| FlMockViewDelegate* view_; |
| FlKeyboardManager* manager_; |
| bool during_redispatch_ = false; |
| |
| static gboolean _flushChannelMessagesCb(gpointer data) { |
| g_autoptr(GMainLoop) loop = reinterpret_cast<GMainLoop*>(data); |
| g_main_loop_quit(loop); |
| return FALSE; |
| } |
| }; |
| |
| // Make sure that the keyboard can be disposed without crashes when there are |
| // unresolved pending events. |
| TEST(FlKeyboardManagerTest, DisposeWithUnresolvedPends) { |
| KeyboardTester tester; |
| std::vector<CallRecord> call_records; |
| |
| // Record calls so that they aren't responded. |
| tester.recordEmbedderCallsTo(call_records); |
| fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| |
| tester.respondToEmbedderCallsWith(true); |
| fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| |
| tester.flushChannelMessages(); |
| |
| // Passes if the cleanup does not crash. |
| } |
| |
| TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) { |
| KeyboardTester tester; |
| std::vector<CallRecord> call_records; |
| std::vector<std::unique_ptr<FlKeyEvent>> redispatched; |
| |
| gboolean manager_handled = false; |
| |
| /// Test 1: One event that is handled by the framework |
| tester.recordEmbedderCallsTo(call_records); |
| tester.recordRedispatchedEventsTo(redispatched); |
| |
| // Dispatch a key event |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_EQ(call_records.size(), 1u); |
| EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", false); |
| |
| call_records[0].callback(true); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| call_records.clear(); |
| |
| /// Test 2: Two events that are unhandled by the framework |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_EQ(call_records.size(), 1u); |
| EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, nullptr, false); |
| |
| // Dispatch another key event |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_b, kKeyCodeKeyB, 0x0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_EQ(call_records.size(), 2u); |
| EXPECT_KEY_EVENT(call_records[1], kFlutterKeyEventTypeDown, kPhysicalKeyB, |
| kLogicalKeyB, "b", false); |
| |
| // Resolve the second event first to test out-of-order response |
| call_records[1].callback(false); |
| EXPECT_EQ(redispatched.size(), 1u); |
| EXPECT_EQ(redispatched[0]->keyval, 0x62u); |
| call_records[0].callback(false); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(redispatched.size(), 2u); |
| EXPECT_EQ(redispatched[1]->keyval, 0x61u); |
| |
| EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| call_records.clear(); |
| |
| // Resolve redispatches |
| EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 2); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(call_records.size(), 0u); |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| |
| /// Test 3: Dispatch the same event again to ensure that prevention from |
| /// redispatching only works once. |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_EQ(call_records.size(), 1u); |
| |
| call_records[0].callback(true); |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| } |
| |
| TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) { |
| KeyboardTester tester; |
| gboolean manager_handled = false; |
| std::vector<CallRecord> call_records; |
| std::vector<std::unique_ptr<FlKeyEvent>> redispatched; |
| |
| /// Test 1: One event that is handled by the framework |
| tester.respondToEmbedderCallsWithAndRecordsTo(true, call_records); |
| tester.recordRedispatchedEventsTo(redispatched); |
| |
| // Dispatch a key event |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(call_records.size(), 1u); |
| EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA, |
| kLogicalKeyA, "a", false); |
| EXPECT_EQ(redispatched.size(), 0u); |
| call_records.clear(); |
| |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| redispatched.clear(); |
| |
| /// Test 2: An event unhandled by the framework |
| tester.respondToEmbedderCallsWithAndRecordsTo(false, call_records); |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(call_records.size(), 1u); |
| EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA, |
| kLogicalKeyA, nullptr, false); |
| EXPECT_EQ(redispatched.size(), 1u); |
| call_records.clear(); |
| |
| EXPECT_FALSE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| |
| EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); |
| EXPECT_EQ(call_records.size(), 0u); |
| |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| } |
| |
| TEST(FlKeyboardManagerTest, WithTwoAsyncDelegates) { |
| KeyboardTester tester; |
| std::vector<CallRecord> call_records; |
| std::vector<std::unique_ptr<FlKeyEvent>> redispatched; |
| |
| gboolean manager_handled = false; |
| |
| tester.recordEmbedderCallsTo(call_records); |
| tester.recordChannelCallsTo(call_records); |
| tester.recordRedispatchedEventsTo(redispatched); |
| |
| /// Test 1: One delegate responds true, the other false |
| |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_EQ(call_records.size(), 2u); |
| |
| EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); |
| EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); |
| |
| call_records[0].callback(true); |
| call_records[1].callback(false); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(redispatched.size(), 0u); |
| |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| call_records.clear(); |
| |
| /// Test 2: All delegates respond false |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(false, GDK_KEY_a, kKeyCodeKeyA, 0x0, false)); |
| |
| EXPECT_EQ(manager_handled, true); |
| EXPECT_EQ(redispatched.size(), 0u); |
| EXPECT_EQ(call_records.size(), 2u); |
| |
| EXPECT_EQ(call_records[0].type, CallRecord::kKeyCallEmbedder); |
| EXPECT_EQ(call_records[1].type, CallRecord::kKeyCallChannel); |
| |
| call_records[0].callback(false); |
| call_records[1].callback(false); |
| |
| call_records.clear(); |
| |
| // Resolve redispatch |
| tester.flushChannelMessages(); |
| EXPECT_EQ(redispatched.size(), 1u); |
| EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); |
| EXPECT_EQ(call_records.size(), 0u); |
| |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| } |
| |
| TEST(FlKeyboardManagerTest, TextInputPluginReturnsFalse) { |
| KeyboardTester tester; |
| std::vector<std::unique_ptr<FlKeyEvent>> redispatched; |
| gboolean manager_handled = false; |
| tester.recordRedispatchedEventsTo(redispatched); |
| tester.respondToTextInputWith(false); |
| |
| // Dispatch a key event. |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| // The event was redispatched because no one handles it. |
| EXPECT_EQ(redispatched.size(), 1u); |
| |
| // Resolve redispatched event. |
| EXPECT_EQ(tester.redispatchEventsAndClear(redispatched), 1); |
| |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| } |
| |
| TEST(FlKeyboardManagerTest, TextInputPluginReturnsTrue) { |
| KeyboardTester tester; |
| std::vector<std::unique_ptr<FlKeyEvent>> redispatched; |
| gboolean manager_handled = false; |
| tester.recordRedispatchedEventsTo(redispatched); |
| tester.respondToTextInputWith(true); |
| |
| // Dispatch a key event. |
| manager_handled = fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, GDK_KEY_a, kKeyCodeKeyA, 0, false)); |
| tester.flushChannelMessages(); |
| EXPECT_EQ(manager_handled, true); |
| // The event was not redispatched because text input plugin handles it. |
| EXPECT_EQ(redispatched.size(), 0u); |
| |
| EXPECT_TRUE(fl_keyboard_manager_is_state_clear(tester.manager())); |
| } |
| |
| TEST(FlKeyboardManagerTest, CorrectLogicalKeyForLayouts) { |
| KeyboardTester tester; |
| |
| std::vector<CallRecord> call_records; |
| tester.recordEmbedderCallsTo(call_records); |
| |
| auto sendTap = [&](guint8 keycode, guint keyval, guint8 group) { |
| fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(true, keyval, keycode, 0, false, group)); |
| fl_keyboard_manager_handle_event( |
| tester.manager(), |
| fl_key_event_new_by_mock(false, keyval, keycode, 0, false, group)); |
| }; |
| |
| /* US keyboard layout */ |
| |
| sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA |
| VERIFY_DOWN(kLogicalKeyA, "a"); |
| |
| sendTap(kKeyCodeKeyA, GDK_KEY_A, 0); // Shift-KeyA |
| VERIFY_DOWN(kLogicalKeyA, "A"); |
| |
| sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 |
| VERIFY_DOWN(kLogicalDigit1, "1"); |
| |
| sendTap(kKeyCodeDigit1, GDK_KEY_exclam, 0); // Shift-Digit1 |
| VERIFY_DOWN(kLogicalDigit1, "!"); |
| |
| sendTap(kKeyCodeMinus, GDK_KEY_minus, 0); // Minus |
| VERIFY_DOWN(kLogicalMinus, "-"); |
| |
| sendTap(kKeyCodeMinus, GDK_KEY_underscore, 0); // Shift-Minus |
| VERIFY_DOWN(kLogicalUnderscore, "_"); |
| |
| /* French keyboard layout, group 3, which is when the input method is showing |
| * "Fr" */ |
| |
| tester.setLayout(kLayoutFrench); |
| |
| sendTap(kKeyCodeKeyA, GDK_KEY_q, 3); // KeyA |
| VERIFY_DOWN(kLogicalKeyQ, "q"); |
| |
| sendTap(kKeyCodeKeyA, GDK_KEY_Q, 3); // Shift-KeyA |
| VERIFY_DOWN(kLogicalKeyQ, "Q"); |
| |
| sendTap(kKeyCodeSemicolon, GDK_KEY_m, 3); // ; but prints M |
| VERIFY_DOWN(kLogicalKeyM, "m"); |
| |
| sendTap(kKeyCodeKeyM, GDK_KEY_comma, 3); // M but prints , |
| VERIFY_DOWN(kLogicalComma, ","); |
| |
| sendTap(kKeyCodeDigit1, GDK_KEY_ampersand, 3); // Digit1 |
| VERIFY_DOWN(kLogicalDigit1, "&"); |
| |
| sendTap(kKeyCodeDigit1, GDK_KEY_1, 3); // Shift-Digit1 |
| VERIFY_DOWN(kLogicalDigit1, "1"); |
| |
| sendTap(kKeyCodeMinus, GDK_KEY_parenright, 3); // Minus |
| VERIFY_DOWN(kLogicalParenthesisRight, ")"); |
| |
| sendTap(kKeyCodeMinus, GDK_KEY_degree, 3); // Shift-Minus |
| VERIFY_DOWN(static_cast<uint32_t>(L'°'), "°"); |
| |
| /* French keyboard layout, group 0, which is pressing the "extra key for |
| * triggering input method" key once after switching to French IME. */ |
| |
| sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA |
| VERIFY_DOWN(kLogicalKeyA, "a"); |
| |
| sendTap(kKeyCodeDigit1, GDK_KEY_1, 0); // Digit1 |
| VERIFY_DOWN(kLogicalDigit1, "1"); |
| |
| /* Russian keyboard layout, group 2 */ |
| tester.setLayout(kLayoutRussian); |
| |
| sendTap(kKeyCodeKeyA, GDK_KEY_Cyrillic_ef, 2); // KeyA |
| VERIFY_DOWN(kLogicalKeyA, "Ñ„"); |
| |
| sendTap(kKeyCodeDigit1, GDK_KEY_1, 2); // Shift-Digit1 |
| VERIFY_DOWN(kLogicalDigit1, "1"); |
| |
| sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_Cyrillic_ha, 2); |
| VERIFY_DOWN(kLogicalBracketLeft, "Ñ…"); |
| |
| /* Russian keyboard layout, group 0 */ |
| sendTap(kKeyCodeKeyA, GDK_KEY_a, 0); // KeyA |
| VERIFY_DOWN(kLogicalKeyA, "a"); |
| |
| sendTap(kKeyCodeKeyLeftBracket, GDK_KEY_bracketleft, 0); |
| 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{{ |
| // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 |
| 0xffff, 0x0031, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 |
| 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c |
| 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 |
| 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 |
| 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 |
| 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c |
| 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 |
| 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 |
| 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 |
| 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c |
| 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 |
| 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 |
| 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 |
| 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c |
| 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 |
| 0xffff, 0xffff, 0x003c, 0x003e, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x64 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 |
| 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c |
| }}; |
| |
| const MockGroupLayoutData kLayoutRussian0{ |
| // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 |
| 0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 |
| 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c |
| 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 |
| 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 |
| 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 |
| 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c |
| 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 |
| 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 |
| 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 |
| 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c |
| 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 |
| 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 |
| 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 |
| 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c |
| 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 |
| 0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c |
| 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 |
| 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 |
| 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c |
| }; |
| |
| const MockGroupLayoutData kLayoutRussian2{{ |
| // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 |
| 0xffff, 0x0031, 0x0021, 0x0000, 0x0031, 0x0021, 0x0032, 0x0022, // 0x08 |
| 0x0033, 0x06b0, 0x0034, 0x003b, 0x0035, 0x0025, 0x0036, 0x003a, // 0x0c |
| 0x0037, 0x003f, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 |
| 0x002d, 0x005f, 0x003d, 0x002b, 0x0071, 0x0051, 0x0000, 0x0000, // 0x14 |
| 0x06ca, 0x06ea, 0x06c3, 0x06e3, 0x06d5, 0x06f5, 0x06cb, 0x06eb, // 0x18 |
| 0x06c5, 0x06e5, 0x06ce, 0x06ee, 0x06c7, 0x06e7, 0x06db, 0x06fb, // 0x1c |
| 0x06dd, 0x06fd, 0x06da, 0x06fa, 0x06c8, 0x06e8, 0x06df, 0x06ff, // 0x20 |
| 0x0061, 0x0041, 0x0041, 0x0000, 0x06c6, 0x06e6, 0x06d9, 0x06f9, // 0x24 |
| 0x06d7, 0x06f7, 0x06c1, 0x06e1, 0x06d0, 0x06f0, 0x06d2, 0x06f2, // 0x28 |
| 0x06cf, 0x06ef, 0x06cc, 0x06ec, 0x06c4, 0x06e4, 0x06d6, 0x06f6, // 0x2c |
| 0x06dc, 0x06fc, 0x06a3, 0x06b3, 0x007c, 0x0000, 0x005c, 0x002f, // 0x30 |
| 0x06d1, 0x06f1, 0x06de, 0x06fe, 0x06d3, 0x06f3, 0x06cd, 0x06ed, // 0x34 |
| 0x06c9, 0x06e9, 0x06d4, 0x06f4, 0x06d8, 0x06f8, 0x06c2, 0x06e2, // 0x38 |
| 0x06c0, 0x06e0, 0x002e, 0x002c, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 |
| 0xffff, 0xffff, 0x003c, 0x003e, 0x002f, 0x007c, 0xffff, 0xffff, // 0x5c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x64 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0x0000, // 0x68 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, // 0x78 |
| 0x00b1, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x7c |
| }}; |
| |
| const MockGroupLayoutData kLayoutFrench0 = { |
| // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 |
| 0x0000, 0xffff, 0xffff, 0x0031, 0x0031, 0x0021, 0x0032, 0x0040, // 0x08 |
| 0x0033, 0x0023, 0x0034, 0x0024, 0x0035, 0x0025, 0x0036, 0x005e, // 0x0c |
| 0x0037, 0x0026, 0x0038, 0x002a, 0x0039, 0x0028, 0x0030, 0x0029, // 0x10 |
| 0x002d, 0x005f, 0x003d, 0x002b, 0xffff, 0xffff, 0xffff, 0xffff, // 0x14 |
| 0x0071, 0x0051, 0x0077, 0x0057, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 |
| 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c |
| 0x006f, 0x004f, 0x0070, 0x0050, 0x005b, 0x007b, 0x005d, 0x007d, // 0x20 |
| 0xffff, 0xffff, 0xffff, 0x0061, 0x0061, 0x0041, 0x0073, 0x0053, // 0x24 |
| 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 |
| 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x003b, 0x003a, // 0x2c |
| 0x0027, 0x0022, 0x0060, 0x007e, 0xffff, 0x005c, 0x005c, 0x007c, // 0x30 |
| 0x007a, 0x005a, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 |
| 0x0062, 0x0042, 0x006e, 0x004e, 0x006d, 0x004d, 0x002c, 0x003c, // 0x38 |
| 0x002e, 0x003e, 0x002f, 0x003f, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c |
| 0xffff, 0xffff, 0x0020, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 |
| 0xffff, 0xffff, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c |
| 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 |
| 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x78 |
| 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, 0xffff, 0xffff, // 0x7c |
| }; |
| |
| const MockGroupLayoutData kLayoutFrench3 = { |
| // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x00 |
| 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0xffff, // 0x04 |
| 0x0000, 0xffff, 0x0000, 0x0000, 0x0026, 0x0031, 0x00e9, 0x0032, // 0x08 |
| 0x0022, 0x0033, 0x0027, 0x0034, 0x0028, 0x0035, 0x002d, 0x0036, // 0x0c |
| 0x00e8, 0x0037, 0x005f, 0x0038, 0x00e7, 0x0039, 0x00e0, 0x0030, // 0x10 |
| 0x0029, 0x00b0, 0x003d, 0x002b, 0x0000, 0x0000, 0x0061, 0x0041, // 0x14 |
| 0x0061, 0x0041, 0x007a, 0x005a, 0x0065, 0x0045, 0x0072, 0x0052, // 0x18 |
| 0x0074, 0x0054, 0x0079, 0x0059, 0x0075, 0x0055, 0x0069, 0x0049, // 0x1c |
| 0x006f, 0x004f, 0x0070, 0x0050, 0xffff, 0xffff, 0x0024, 0x00a3, // 0x20 |
| 0x0041, 0x0000, 0x0000, 0x0000, 0x0071, 0x0051, 0x0073, 0x0053, // 0x24 |
| 0x0064, 0x0044, 0x0066, 0x0046, 0x0067, 0x0047, 0x0068, 0x0048, // 0x28 |
| 0x006a, 0x004a, 0x006b, 0x004b, 0x006c, 0x004c, 0x006d, 0x004d, // 0x2c |
| 0x00f9, 0x0025, 0x00b2, 0x007e, 0x0000, 0x0000, 0x002a, 0x00b5, // 0x30 |
| 0x0077, 0x0057, 0x0078, 0x0058, 0x0063, 0x0043, 0x0076, 0x0056, // 0x34 |
| 0x0062, 0x0042, 0x006e, 0x004e, 0x002c, 0x003f, 0x003b, 0x002e, // 0x38 |
| 0x003a, 0x002f, 0x0021, 0x00a7, 0xffff, 0xffff, 0xffff, 0xffff, // 0x3c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x40 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x44 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x48 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x4c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x50 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x54 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x58 |
| 0xffff, 0x003c, 0x0000, 0xffff, 0x003c, 0x003e, 0xffff, 0xffff, // 0x5c |
| 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x60 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, // 0x64 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x68 |
| 0xffff, 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x6c |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x70 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x74 |
| 0x0000, 0xffff, 0xffff, 0xffff, 0xffff, 0x00b1, 0x00b1, 0xffff, // 0x78 |
| 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, // 0x7c |
| }; |
| |
| const MockLayoutData kLayoutUs{&kLayoutUs0}; |
| const MockLayoutData kLayoutRussian{&kLayoutRussian0, nullptr, |
| &kLayoutRussian2}; |
| const MockLayoutData kLayoutFrench{&kLayoutFrench0, nullptr, nullptr, |
| &kLayoutFrench3}; |
| |
| } // namespace |