| // 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 |