mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[Linux, Keyboard] Derive keyboard layout using printable information from system (flutter/engine#32665)
* Workable layout deduction * Compilable test * Tests * Remove print, format * Use new way to pass logical key * Use unique_ptr * Format * Fix matrix format * Format * Cleanup * Remove duplicate * Remove printf * Update shell/platform/linux/fl_keyboard_manager.cc Co-authored-by: Greg Spencer <gspencergoog@users.noreply.github.com> Co-authored-by: Greg Spencer <gspencergoog@users.noreply.github.com>
This commit is contained in:
parent
bcd33ce3a6
commit
d5820083a5
@ -283,7 +283,7 @@ typedef _Nullable _NSResponderPtr (^NextResponderProvider)();
|
||||
//
|
||||
// - Mandatory goal, if it matches any clue. This ensures that all alnum
|
||||
// keys can be found somewhere.
|
||||
// - US layout, if neither clue of the key is EASCII. This ensures that
|
||||
// - US layout, if neither clue of the key is EASCII. This ensures that
|
||||
// there are no non-latin logical keys.
|
||||
// - Derived on the fly from keyCode & characters.
|
||||
for (const LayoutClue& clue : thisKeyClues) {
|
||||
|
||||
@ -35,6 +35,7 @@ constexpr uint64_t kPhysicalSuspend = 0x00000014;
|
||||
constexpr uint64_t kPhysicalResume = 0x00000015;
|
||||
constexpr uint64_t kPhysicalTurbo = 0x00000016;
|
||||
constexpr uint64_t kPhysicalPrivacyScreenToggle = 0x00000017;
|
||||
constexpr uint64_t kPhysicalMicrophoneMuteToggle = 0x00000018;
|
||||
constexpr uint64_t kPhysicalSleep = 0x00010082;
|
||||
constexpr uint64_t kPhysicalWakeUp = 0x00010083;
|
||||
constexpr uint64_t kPhysicalDisplayToggleIntExt = 0x000100b5;
|
||||
|
||||
@ -19,6 +19,7 @@ static constexpr char kKeyCodeKey[] = "keyCode";
|
||||
static constexpr char kScanCodeKey[] = "scanCode";
|
||||
static constexpr char kModifiersKey[] = "modifiers";
|
||||
static constexpr char kToolkitKey[] = "toolkit";
|
||||
static constexpr char kSpecifiedLogicalKey[] = "specifiedLogicalKey";
|
||||
static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues";
|
||||
|
||||
static constexpr char kGtkToolkit[] = "gtk";
|
||||
@ -115,6 +116,7 @@ G_DEFINE_TYPE_WITH_CODE(
|
||||
static void fl_key_channel_responder_handle_event(
|
||||
FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
uint64_t specified_logical_key,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
@ -203,6 +205,7 @@ FlKeyChannelResponder* fl_key_channel_responder_new(
|
||||
static void fl_key_channel_responder_handle_event(
|
||||
FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
uint64_t specified_logical_key,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data) {
|
||||
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(responder);
|
||||
@ -273,6 +276,11 @@ static void fl_key_channel_responder_handle_event(
|
||||
fl_value_new_int(unicode_scarlar_values));
|
||||
}
|
||||
|
||||
if (specified_logical_key != 0) {
|
||||
fl_value_set_string_take(message, kSpecifiedLogicalKey,
|
||||
fl_value_new_int(specified_logical_key));
|
||||
}
|
||||
|
||||
FlKeyChannelUserData* data =
|
||||
fl_key_channel_user_data_new(self, callback, user_data);
|
||||
// Send the message off to the framework for handling (or not).
|
||||
|
||||
@ -203,3 +203,30 @@ TEST(FlKeyChannelResponderTest, TestKeyEventHandledByFramework) {
|
||||
// Blocks here until echo_response_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
TEST(FlKeyChannelResponderTest, UseSpecifiedLogicalKey) {
|
||||
g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
|
||||
|
||||
g_autoptr(FlEngine) engine = make_mock_engine();
|
||||
g_autoptr(FlBinaryMessenger) messenger = fl_binary_messenger_new(engine);
|
||||
FlKeyChannelResponderMock mock{
|
||||
.value_converter = echo_response_cb,
|
||||
.channel_name = "test/echo",
|
||||
};
|
||||
g_autoptr(FlKeyResponder) responder =
|
||||
FL_KEY_RESPONDER(fl_key_channel_responder_new(messenger, &mock));
|
||||
|
||||
fl_key_responder_handle_event(
|
||||
responder,
|
||||
fl_key_event_new_by_mock(12345, true, GDK_KEY_A, 0x04, 0x0, nullptr,
|
||||
false),
|
||||
responder_callback, loop, 888);
|
||||
expected_handled = TRUE;
|
||||
expected_value =
|
||||
"{type: keydown, keymap: linux, scanCode: 4, toolkit: gtk, "
|
||||
"keyCode: 65, modifiers: 0, unicodeScalarValues: 65, "
|
||||
"specifiedLogicalKey: 888}";
|
||||
|
||||
// Blocks here until echo_response_cb is called.
|
||||
g_main_loop_run(loop);
|
||||
}
|
||||
|
||||
@ -212,6 +212,7 @@ G_DEFINE_TYPE_WITH_CODE(
|
||||
static void fl_key_embedder_responder_handle_event(
|
||||
FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
uint64_t specified_logical_key,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data);
|
||||
|
||||
@ -698,6 +699,7 @@ static void synchronize_lock_states_loop_body(gpointer key,
|
||||
static void fl_key_embedder_responder_handle_event_impl(
|
||||
FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
uint64_t specified_logical_key,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data) {
|
||||
FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder);
|
||||
@ -706,7 +708,9 @@ static void fl_key_embedder_responder_handle_event_impl(
|
||||
g_return_if_fail(callback != nullptr);
|
||||
|
||||
const uint64_t physical_key = event_to_physical_key(event);
|
||||
const uint64_t logical_key = event_to_logical_key(event);
|
||||
const uint64_t logical_key = specified_logical_key != 0
|
||||
? specified_logical_key
|
||||
: event_to_logical_key(event);
|
||||
const double timestamp = event_to_timestamp(event);
|
||||
const bool is_down_event = event->is_press;
|
||||
|
||||
@ -777,12 +781,13 @@ static void fl_key_embedder_responder_handle_event_impl(
|
||||
static void fl_key_embedder_responder_handle_event(
|
||||
FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
uint64_t specified_logical_key,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data) {
|
||||
FlKeyEmbedderResponder* self = FL_KEY_EMBEDDER_RESPONDER(responder);
|
||||
self->sent_any_events = false;
|
||||
fl_key_embedder_responder_handle_event_impl(responder, event, callback,
|
||||
user_data);
|
||||
fl_key_embedder_responder_handle_event_impl(
|
||||
responder, event, specified_logical_key, callback, user_data);
|
||||
if (!self->sent_any_events) {
|
||||
self->send_key_event(&empty_event, nullptr, nullptr);
|
||||
}
|
||||
|
||||
@ -194,8 +194,6 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) {
|
||||
invoke_record_callback_and_verify(record, TRUE, &user_data);
|
||||
g_ptr_array_clear(g_call_records);
|
||||
|
||||
// Skip testing key repeats, which is not present on GDK.
|
||||
|
||||
// Key up
|
||||
fl_key_responder_handle_event(
|
||||
responder,
|
||||
@ -261,6 +259,41 @@ TEST(FlKeyEmbedderResponderTest, SendKeyEvent) {
|
||||
g_object_unref(responder);
|
||||
}
|
||||
|
||||
// Basic key presses, but uses the specified logical key if it is not 0.
|
||||
TEST(FlKeyEmbedderResponderTest, UsesSpecifiedLogicalKey) {
|
||||
EXPECT_EQ(g_call_records, nullptr);
|
||||
g_call_records = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
FlKeyResponder* responder = FL_KEY_RESPONDER(
|
||||
fl_key_embedder_responder_new(record_calls_in(g_call_records)));
|
||||
int user_data = 123; // Arbitrary user data
|
||||
|
||||
FlKeyEmbedderCallRecord* record;
|
||||
|
||||
// On an AZERTY keyboard, press physical key 1, and release.
|
||||
// Key down
|
||||
fl_key_responder_handle_event(
|
||||
responder,
|
||||
fl_key_event_new_by_mock(12345, kPress, GDK_KEY_ampersand, kKeyCodeDigit1,
|
||||
0, kIsNotModifier),
|
||||
verify_response_handled, &user_data, kLogicalDigit1);
|
||||
|
||||
EXPECT_EQ(g_call_records->len, 1u);
|
||||
record = FL_KEY_EMBEDDER_CALL_RECORD(g_ptr_array_index(g_call_records, 0));
|
||||
EXPECT_EQ(record->event->struct_size, sizeof(FlutterKeyEvent));
|
||||
EXPECT_EQ(record->event->timestamp, 12345000);
|
||||
EXPECT_EQ(record->event->type, kFlutterKeyEventTypeDown);
|
||||
EXPECT_EQ(record->event->physical, kPhysicalDigit1);
|
||||
EXPECT_EQ(record->event->logical, kLogicalDigit1);
|
||||
EXPECT_STREQ(record->event->character, "&");
|
||||
EXPECT_EQ(record->event->synthesized, false);
|
||||
|
||||
invoke_record_callback_and_verify(record, TRUE, &user_data);
|
||||
g_ptr_array_clear(g_call_records);
|
||||
|
||||
clear_g_call_records();
|
||||
g_object_unref(responder);
|
||||
}
|
||||
|
||||
// Press Shift, key A, then release Shift, key A.
|
||||
TEST(FlKeyEmbedderResponderTest, PressShiftDuringLetterKeyTap) {
|
||||
EXPECT_EQ(g_call_records, nullptr);
|
||||
|
||||
@ -33,6 +33,7 @@ FlKeyEvent* fl_key_event_new_from_gdk_event(GdkEvent* raw_event) {
|
||||
result->keyval = event->keyval;
|
||||
result->state = event->state;
|
||||
result->string = clone_string(event->string);
|
||||
result->group = event->group;
|
||||
result->origin = event;
|
||||
result->dispose_origin = dispose_origin_from_gdk_event;
|
||||
|
||||
|
||||
@ -39,6 +39,8 @@ typedef struct _FlKeyEvent {
|
||||
guint keyval;
|
||||
// Modifier state.
|
||||
int state;
|
||||
// Keyboard group.
|
||||
guint8 group;
|
||||
// String, null-terminated.
|
||||
//
|
||||
// Can be nullptr.
|
||||
|
||||
@ -11,11 +11,12 @@ static void fl_key_responder_default_init(FlKeyResponderInterface* iface) {}
|
||||
void fl_key_responder_handle_event(FlKeyResponder* self,
|
||||
FlKeyEvent* event,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data) {
|
||||
gpointer user_data,
|
||||
uint64_t specified_logical_key) {
|
||||
g_return_if_fail(FL_IS_KEY_RESPONDER(self));
|
||||
g_return_if_fail(event != nullptr);
|
||||
g_return_if_fail(callback != nullptr);
|
||||
|
||||
FL_KEY_RESPONDER_GET_IFACE(self)->handle_event(self, event, callback,
|
||||
user_data);
|
||||
FL_KEY_RESPONDER_GET_IFACE(self)->handle_event(
|
||||
self, event, specified_logical_key, callback, user_data);
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ struct _FlKeyResponderInterface {
|
||||
*/
|
||||
void (*handle_event)(FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
uint64_t specified_logical_key,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data);
|
||||
};
|
||||
@ -74,7 +75,8 @@ struct _FlKeyResponderInterface {
|
||||
void fl_key_responder_handle_event(FlKeyResponder* responder,
|
||||
FlKeyEvent* event,
|
||||
FlKeyResponderAsyncCallback callback,
|
||||
gpointer user_data);
|
||||
gpointer user_data,
|
||||
uint64_t specified_logical_key = 0);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
||||
@ -4,13 +4,107 @@
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
|
||||
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
|
||||
#include "flutter/shell/platform/linux/fl_key_embedder_responder.h"
|
||||
#include "flutter/shell/platform/linux/key_mapping.h"
|
||||
|
||||
/* Declare and define FlKeyboardPendingEvent */
|
||||
// Turn on this flag to print complete layout data when switching IMEs. The data
|
||||
// is used in unit tests.
|
||||
#define DEBUG_PRINT_LAYOUT
|
||||
|
||||
/* Declarations of private classes */
|
||||
|
||||
G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
|
||||
fl_keyboard_pending_event,
|
||||
FL,
|
||||
KEYBOARD_PENDING_EVENT,
|
||||
GObject);
|
||||
|
||||
#define FL_TYPE_KEYBOARD_MANAGER_USER_DATA \
|
||||
fl_keyboard_manager_user_data_get_type()
|
||||
G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData,
|
||||
fl_keyboard_manager_user_data,
|
||||
FL,
|
||||
KEYBOARD_MANAGER_USER_DATA,
|
||||
GObject);
|
||||
|
||||
/* End declarations */
|
||||
|
||||
namespace {
|
||||
|
||||
// The maxiumum keycode in a derived layout.
|
||||
//
|
||||
// Although X supports higher keycodes, Flutter only cares about standard keys,
|
||||
// which are below this.
|
||||
constexpr size_t kLayoutSize = 128;
|
||||
// Describes the derived layout of a keyboard group.
|
||||
//
|
||||
// Maps from keycode to logical key. Value being 0 stands for empty.
|
||||
typedef std::array<uint64_t, kLayoutSize> DerivedGroupLayout;
|
||||
// Describes the derived layout of the entire keyboard.
|
||||
//
|
||||
// Maps from group ID to group layout.
|
||||
typedef std::map<guint8, DerivedGroupLayout> DerivedLayout;
|
||||
|
||||
// Context variables for the foreach call used to dispatch events to responders.
|
||||
typedef struct {
|
||||
FlKeyEvent* event;
|
||||
uint64_t specified_logical_key;
|
||||
FlKeyboardManagerUserData* user_data;
|
||||
} DispatchToResponderLoopContext;
|
||||
|
||||
bool is_eascii(uint16_t character) {
|
||||
return character < 256;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PRINT_LAYOUT
|
||||
// Prints layout entries that will be parsed by `MockLayoutData`.
|
||||
void debug_format_layout_data(std::string& debug_layout_data,
|
||||
uint16_t keycode,
|
||||
uint16_t clue1,
|
||||
uint16_t clue2) {
|
||||
if (keycode % 4 == 0) {
|
||||
debug_layout_data.append(" ");
|
||||
}
|
||||
|
||||
constexpr int kBufferSize = 30;
|
||||
char buffer[kBufferSize];
|
||||
buffer[0] = 0;
|
||||
buffer[kBufferSize - 1] = 0;
|
||||
|
||||
snprintf(buffer, kBufferSize, "0x%04x, 0x%04x, ", clue1, clue2);
|
||||
debug_layout_data.append(buffer);
|
||||
|
||||
if (keycode % 4 == 3) {
|
||||
snprintf(buffer, kBufferSize, " // 0x%02x", keycode);
|
||||
debug_layout_data.append(buffer);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
static uint64_t get_logical_key_from_layout(const FlKeyEvent* event,
|
||||
const DerivedLayout& layout) {
|
||||
guint8 group = event->group;
|
||||
guint16 keycode = event->keycode;
|
||||
if (keycode >= kLayoutSize) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto found_group_layout = layout.find(group);
|
||||
if (found_group_layout != layout.end()) {
|
||||
return found_group_layout->second[keycode];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Define FlKeyboardPendingEvent */
|
||||
|
||||
/**
|
||||
* FlKeyboardPendingEvent:
|
||||
@ -20,11 +114,6 @@
|
||||
* This object is used by both the "pending_responds" list and the
|
||||
* "pending_redispatches" list.
|
||||
*/
|
||||
G_DECLARE_FINAL_TYPE(FlKeyboardPendingEvent,
|
||||
fl_keyboard_pending_event,
|
||||
FL,
|
||||
KEYBOARD_PENDING_EVENT,
|
||||
GObject);
|
||||
|
||||
struct _FlKeyboardPendingEvent {
|
||||
GObject parent_instance;
|
||||
@ -87,7 +176,7 @@ static uint64_t fl_keyboard_manager_get_event_hash(FlKeyEvent* event) {
|
||||
// the sequence ID, and the number of responders that will reply.
|
||||
//
|
||||
// This will acquire the ownership of the event.
|
||||
FlKeyboardPendingEvent* fl_keyboard_pending_event_new(
|
||||
static FlKeyboardPendingEvent* fl_keyboard_pending_event_new(
|
||||
std::unique_ptr<FlKeyEvent> event,
|
||||
uint64_t sequence_id,
|
||||
size_t to_reply) {
|
||||
@ -102,20 +191,13 @@ FlKeyboardPendingEvent* fl_keyboard_pending_event_new(
|
||||
return self;
|
||||
}
|
||||
|
||||
/* Declare and define FlKeyboardManagerUserData */
|
||||
/* Define FlKeyboardManagerUserData */
|
||||
|
||||
/**
|
||||
* FlKeyboardManagerUserData:
|
||||
* The user_data used when #FlKeyboardManager sends event to
|
||||
* responders.
|
||||
*/
|
||||
#define FL_TYPE_KEYBOARD_MANAGER_USER_DATA \
|
||||
fl_keyboard_manager_user_data_get_type()
|
||||
G_DECLARE_FINAL_TYPE(FlKeyboardManagerUserData,
|
||||
fl_keyboard_manager_user_data,
|
||||
FL,
|
||||
KEYBOARD_MANAGER_USER_DATA,
|
||||
GObject);
|
||||
|
||||
struct _FlKeyboardManagerUserData {
|
||||
GObject parent_instance;
|
||||
@ -125,16 +207,6 @@ struct _FlKeyboardManagerUserData {
|
||||
uint64_t sequence_id;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// Context variables for the foreach call used to dispatch events to responders.
|
||||
typedef struct {
|
||||
FlKeyEvent* event;
|
||||
FlKeyboardManagerUserData* user_data;
|
||||
} DispatchToResponderLoopContext;
|
||||
|
||||
} // namespace
|
||||
|
||||
G_DEFINE_TYPE(FlKeyboardManagerUserData,
|
||||
fl_keyboard_manager_user_data,
|
||||
G_TYPE_OBJECT)
|
||||
@ -158,7 +230,7 @@ static void fl_keyboard_manager_user_data_init(
|
||||
FlKeyboardManagerUserData* self) {}
|
||||
|
||||
// Creates a new FlKeyboardManagerUserData private class with all information.
|
||||
FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new(
|
||||
static FlKeyboardManagerUserData* fl_keyboard_manager_user_data_new(
|
||||
FlKeyboardManager* manager,
|
||||
uint64_t sequence_id) {
|
||||
FlKeyboardManagerUserData* self = FL_KEYBOARD_MANAGER_USER_DATA(
|
||||
@ -199,6 +271,22 @@ struct _FlKeyboardManager {
|
||||
|
||||
// The last sequence ID used. Increased by 1 by every use.
|
||||
uint64_t last_sequence_id;
|
||||
|
||||
// Record the derived layout.
|
||||
//
|
||||
// It is cleared when the platform reports a layout switch. Each entry,
|
||||
// which corresponds to a group, is only initialized on the arrival of the
|
||||
// first event for that group that has a goal keycode.
|
||||
std::unique_ptr<DerivedLayout> derived_layout;
|
||||
// A static map from keycodes to all layout goals.
|
||||
//
|
||||
// It is set up when the manager is initialized and is not changed ever after.
|
||||
std::unique_ptr<std::map<uint16_t, const LayoutGoal*>> keycode_to_goals;
|
||||
// A static map from logical keys to all mandatory layout goals.
|
||||
//
|
||||
// It is set up when the manager is initialized and is not changed ever after.
|
||||
std::unique_ptr<std::map<uint64_t, const LayoutGoal*>>
|
||||
logical_to_mandatory_goals;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT);
|
||||
@ -209,11 +297,44 @@ static void fl_keyboard_manager_class_init(FlKeyboardManagerClass* klass) {
|
||||
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_dispose;
|
||||
}
|
||||
|
||||
static void fl_keyboard_manager_init(FlKeyboardManager* self) {}
|
||||
static void fl_keyboard_manager_init(FlKeyboardManager* self) {
|
||||
self->derived_layout = std::make_unique<DerivedLayout>();
|
||||
|
||||
self->keycode_to_goals =
|
||||
std::make_unique<std::map<uint16_t, const LayoutGoal*>>();
|
||||
self->logical_to_mandatory_goals =
|
||||
std::make_unique<std::map<uint64_t, const LayoutGoal*>>();
|
||||
for (const LayoutGoal& goal : layout_goals) {
|
||||
(*self->keycode_to_goals)[goal.keycode] = &goal;
|
||||
if (goal.mandatory) {
|
||||
(*self->logical_to_mandatory_goals)[goal.logical_key] = &goal;
|
||||
}
|
||||
}
|
||||
|
||||
self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
|
||||
self->pending_responds = g_ptr_array_new();
|
||||
self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
|
||||
self->last_sequence_id = 1;
|
||||
}
|
||||
|
||||
static void fl_keyboard_manager_dispose(GObject* object) {
|
||||
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object);
|
||||
|
||||
if (self->view_delegate != nullptr) {
|
||||
fl_keyboard_view_delegate_subscribe_to_layout_change(self->view_delegate,
|
||||
nullptr);
|
||||
g_object_remove_weak_pointer(
|
||||
G_OBJECT(self->view_delegate),
|
||||
reinterpret_cast<gpointer*>(&(self->view_delegate)));
|
||||
self->view_delegate = nullptr;
|
||||
}
|
||||
|
||||
self->derived_layout.reset();
|
||||
self->keycode_to_goals.reset();
|
||||
self->logical_to_mandatory_goals.reset();
|
||||
|
||||
g_ptr_array_free(self->responder_list, TRUE);
|
||||
g_ptr_array_set_free_func(self->pending_responds, g_object_unref);
|
||||
g_ptr_array_free(self->pending_responds, TRUE);
|
||||
@ -227,10 +348,10 @@ static void fl_keyboard_manager_dispose(GObject* object) {
|
||||
// This is an exact copy of g_ptr_array_find_with_equal_func. Somehow CI
|
||||
// reports that can not find symbol g_ptr_array_find_with_equal_func, despite
|
||||
// the fact that it runs well locally.
|
||||
gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
|
||||
gconstpointer needle,
|
||||
GEqualFunc equal_func,
|
||||
guint* index_) {
|
||||
static gboolean g_ptr_array_find_with_equal_func1(GPtrArray* haystack,
|
||||
gconstpointer needle,
|
||||
GEqualFunc equal_func,
|
||||
guint* index_) {
|
||||
guint i;
|
||||
g_return_val_if_fail(haystack != NULL, FALSE);
|
||||
if (equal_func == NULL) {
|
||||
@ -292,6 +413,7 @@ static void responder_handle_event_callback(bool handled,
|
||||
FlKeyboardManagerUserData* user_data =
|
||||
FL_KEYBOARD_MANAGER_USER_DATA(user_data_ptr);
|
||||
FlKeyboardManager* self = user_data->manager;
|
||||
g_return_if_fail(self->view_delegate != nullptr);
|
||||
|
||||
guint result_index = -1;
|
||||
gboolean found = g_ptr_array_find_with_equal_func1(
|
||||
@ -323,6 +445,93 @@ static void responder_handle_event_callback(bool handled,
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t convert_key_to_char(FlKeyboardViewDelegate* view_delegate,
|
||||
guint keycode,
|
||||
gint group,
|
||||
gint level) {
|
||||
GdkKeymapKey key = {keycode, group, level};
|
||||
constexpr int kBmpMax = 0xD7FF;
|
||||
guint origin = fl_keyboard_view_delegate_lookup_key(view_delegate, &key);
|
||||
return origin < kBmpMax ? origin : 0xFFFF;
|
||||
}
|
||||
|
||||
// Make sure that Flutter has derived the layout for the group of the event,
|
||||
// if the event contains a goal keycode.
|
||||
static void guarantee_layout(FlKeyboardManager* self, FlKeyEvent* event) {
|
||||
guint8 group = event->group;
|
||||
if (self->derived_layout->find(group) != self->derived_layout->end()) {
|
||||
return;
|
||||
}
|
||||
if (self->keycode_to_goals->find(event->keycode) ==
|
||||
self->keycode_to_goals->end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DerivedGroupLayout& layout = (*self->derived_layout)[group];
|
||||
|
||||
// Clone all mandatory goals. Each goal is removed from this cloned map when
|
||||
// fulfilled, and the remaining ones will be assigned to a default position.
|
||||
std::map<uint64_t, const LayoutGoal*> remaining_mandatory_goals =
|
||||
*self->logical_to_mandatory_goals;
|
||||
|
||||
#ifdef DEBUG_PRINT_LAYOUT
|
||||
std::string debug_layout_data;
|
||||
for (uint16_t keycode = 0; keycode < 128; keycode += 1) {
|
||||
std::vector<uint16_t> this_key_clues = {
|
||||
convert_key_to_char(self->view_delegate, keycode, group, 0),
|
||||
convert_key_to_char(self->view_delegate, keycode, group, 1), // Shift
|
||||
};
|
||||
debug_format_layout_data(debug_layout_data, keycode, this_key_clues[0],
|
||||
this_key_clues[1]);
|
||||
}
|
||||
#endif
|
||||
|
||||
// It's important to only traverse layout goals instead of all keycodes.
|
||||
// Some key codes outside of the standard keyboard also gives alpha-numeric
|
||||
// letters, and will therefore take over mandatory goals from standard
|
||||
// keyboard keys if they come first. Example: French keyboard digit 1.
|
||||
for (const LayoutGoal& keycode_goal : layout_goals) {
|
||||
uint16_t keycode = keycode_goal.keycode;
|
||||
std::vector<uint16_t> this_key_clues = {
|
||||
convert_key_to_char(self->view_delegate, keycode, group, 0),
|
||||
convert_key_to_char(self->view_delegate, keycode, group, 1), // Shift
|
||||
};
|
||||
|
||||
// The logical key should be the first available clue from below:
|
||||
//
|
||||
// - Mandatory goal, if it matches any clue. This ensures that all alnum
|
||||
// keys can be found somewhere.
|
||||
// - US layout, if neither clue of the key is EASCII. This ensures that
|
||||
// there are no non-latin logical keys.
|
||||
// - A value derived on the fly from keycode & keyval.
|
||||
for (uint16_t clue : this_key_clues) {
|
||||
auto matching_goal = remaining_mandatory_goals.find(clue);
|
||||
if (matching_goal != remaining_mandatory_goals.end()) {
|
||||
// Found a key that produces a mandatory char. Use it.
|
||||
g_return_if_fail(layout[keycode] == 0);
|
||||
layout[keycode] = clue;
|
||||
remaining_mandatory_goals.erase(matching_goal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool has_any_eascii =
|
||||
is_eascii(this_key_clues[0]) || is_eascii(this_key_clues[1]);
|
||||
// See if any produced char meets the requirement as a logical key.
|
||||
if (layout[keycode] == 0 && !has_any_eascii) {
|
||||
auto found_us_layout = self->keycode_to_goals->find(keycode);
|
||||
if (found_us_layout != self->keycode_to_goals->end()) {
|
||||
layout[keycode] = found_us_layout->second->logical_key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all mandatory goals are assigned.
|
||||
for (const auto mandatory_goal_iter : remaining_mandatory_goals) {
|
||||
const LayoutGoal* goal = mandatory_goal_iter.second;
|
||||
layout[goal->keycode] = goal->logical_key;
|
||||
}
|
||||
}
|
||||
|
||||
FlKeyboardManager* fl_keyboard_manager_new(
|
||||
FlKeyboardViewDelegate* view_delegate) {
|
||||
g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(view_delegate), nullptr);
|
||||
@ -331,12 +540,9 @@ FlKeyboardManager* fl_keyboard_manager_new(
|
||||
g_object_new(fl_keyboard_manager_get_type(), nullptr));
|
||||
|
||||
self->view_delegate = view_delegate;
|
||||
self->responder_list = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
|
||||
self->pending_responds = g_ptr_array_new();
|
||||
self->pending_redispatches = g_ptr_array_new_with_free_func(g_object_unref);
|
||||
|
||||
self->last_sequence_id = 1;
|
||||
g_object_add_weak_pointer(
|
||||
G_OBJECT(view_delegate),
|
||||
reinterpret_cast<gpointer*>(&(self->view_delegate)));
|
||||
|
||||
// The embedder responder must be added before the channel responder.
|
||||
g_ptr_array_add(
|
||||
@ -344,6 +550,7 @@ FlKeyboardManager* fl_keyboard_manager_new(
|
||||
FL_KEY_RESPONDER(fl_key_embedder_responder_new(
|
||||
[self](const FlutterKeyEvent* event, FlutterKeyEventCallback callback,
|
||||
void* user_data) {
|
||||
g_return_if_fail(self->view_delegate != nullptr);
|
||||
fl_keyboard_view_delegate_send_key_event(self->view_delegate, event,
|
||||
callback, user_data);
|
||||
})));
|
||||
@ -351,6 +558,8 @@ FlKeyboardManager* fl_keyboard_manager_new(
|
||||
FL_KEY_RESPONDER(fl_key_channel_responder_new(
|
||||
fl_keyboard_view_delegate_get_messenger(view_delegate))));
|
||||
|
||||
fl_keyboard_view_delegate_subscribe_to_layout_change(
|
||||
self->view_delegate, [self]() { self->derived_layout->clear(); });
|
||||
return self;
|
||||
}
|
||||
|
||||
@ -360,15 +569,18 @@ static void dispatch_to_responder(gpointer responder_data,
|
||||
DispatchToResponderLoopContext* context =
|
||||
reinterpret_cast<DispatchToResponderLoopContext*>(foreach_data_ptr);
|
||||
FlKeyResponder* responder = FL_KEY_RESPONDER(responder_data);
|
||||
fl_key_responder_handle_event(responder, context->event,
|
||||
responder_handle_event_callback,
|
||||
context->user_data);
|
||||
fl_key_responder_handle_event(
|
||||
responder, context->event, responder_handle_event_callback,
|
||||
context->user_data, context->specified_logical_key);
|
||||
}
|
||||
|
||||
gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self,
|
||||
FlKeyEvent* event) {
|
||||
g_return_val_if_fail(FL_IS_KEYBOARD_MANAGER(self), FALSE);
|
||||
g_return_val_if_fail(event != nullptr, FALSE);
|
||||
g_return_val_if_fail(self->view_delegate != nullptr, FALSE);
|
||||
|
||||
guarantee_layout(self, event);
|
||||
|
||||
uint64_t incoming_hash = fl_keyboard_manager_get_event_hash(event);
|
||||
if (fl_keyboard_manager_remove_redispatched(self, incoming_hash)) {
|
||||
@ -384,6 +596,8 @@ gboolean fl_keyboard_manager_handle_event(FlKeyboardManager* self,
|
||||
fl_keyboard_manager_user_data_new(self, pending->sequence_id);
|
||||
DispatchToResponderLoopContext data{
|
||||
.event = event,
|
||||
.specified_logical_key =
|
||||
get_logical_key_from_layout(event, *self->derived_layout),
|
||||
.user_data = user_data,
|
||||
};
|
||||
g_ptr_array_foreach(self->responder_list, dispatch_to_responder, &data);
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
#include <functional>
|
||||
|
||||
#include "flutter/shell/platform/linux/fl_keyboard_view_delegate.h"
|
||||
|
||||
|
||||
@ -15,16 +15,35 @@
|
||||
// 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(EVENT, TYPE, PHYSICAL, LOGICAL, CHAR, SYNTHESIZED) \
|
||||
EXPECT_EQ((EVENT)->type, (TYPE)); \
|
||||
EXPECT_EQ((EVENT)->physical, (PHYSICAL)); \
|
||||
EXPECT_EQ((EVENT)->logical, (LOGICAL)); \
|
||||
EXPECT_STREQ((EVENT)->character, (CHAR)); \
|
||||
EXPECT_EQ((EVENT)->synthesized, (SYNTHESIZED));
|
||||
#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::kLogicalBracketLeft;
|
||||
using ::flutter::testing::keycodes::kLogicalComma;
|
||||
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::kLogicalMinus;
|
||||
using ::flutter::testing::keycodes::kLogicalParenthesisRight;
|
||||
using ::flutter::testing::keycodes::kLogicalSemicolon;
|
||||
using ::flutter::testing::keycodes::kLogicalUnderscore;
|
||||
|
||||
using ::flutter::testing::keycodes::kPhysicalKeyA;
|
||||
using ::flutter::testing::keycodes::kPhysicalKeyB;
|
||||
|
||||
@ -66,9 +85,26 @@ char* cloneString(const char* source) {
|
||||
|
||||
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,
|
||||
@ -194,6 +230,8 @@ struct _FlMockViewDelegate {
|
||||
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(
|
||||
@ -257,12 +295,34 @@ static void fl_mock_view_keyboard_redispatch_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() {
|
||||
@ -292,6 +352,14 @@ static void fl_mock_view_set_redispatch_handler(FlMockViewDelegate* self,
|
||||
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
|
||||
@ -311,13 +379,15 @@ static FlKeyEvent* fl_key_event_new_by_mock(bool is_press,
|
||||
guint keyval,
|
||||
guint16 keycode,
|
||||
int state,
|
||||
gboolean is_modifier) {
|
||||
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;
|
||||
@ -332,6 +402,7 @@ class KeyboardTester {
|
||||
respondToEmbedderCallsWith(false);
|
||||
respondToChannelCallsWith(false);
|
||||
respondToTextInputWith(false);
|
||||
setLayout(kLayoutUs);
|
||||
|
||||
manager_ = fl_keyboard_manager_new(FL_KEYBOARD_VIEW_DELEGATE(view_));
|
||||
}
|
||||
@ -451,6 +522,10 @@ class KeyboardTester {
|
||||
});
|
||||
}
|
||||
|
||||
void setLayout(const MockLayoutData& layout) {
|
||||
fl_mock_view_set_layout(view_, &layout);
|
||||
}
|
||||
|
||||
private:
|
||||
FlMockViewDelegate* view_;
|
||||
FlKeyboardManager* manager_;
|
||||
@ -504,8 +579,8 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) {
|
||||
EXPECT_EQ(manager_handled, true);
|
||||
EXPECT_EQ(redispatched.size(), 0u);
|
||||
EXPECT_EQ(call_records.size(), 1u);
|
||||
EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeDown,
|
||||
kPhysicalKeyA, kLogicalKeyA, "a", false);
|
||||
EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
|
||||
kLogicalKeyA, "a", false);
|
||||
|
||||
call_records[0].callback(true);
|
||||
tester.flushChannelMessages();
|
||||
@ -521,7 +596,7 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) {
|
||||
EXPECT_EQ(manager_handled, true);
|
||||
EXPECT_EQ(redispatched.size(), 0u);
|
||||
EXPECT_EQ(call_records.size(), 1u);
|
||||
EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeUp, kPhysicalKeyA,
|
||||
EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
|
||||
kLogicalKeyA, nullptr, false);
|
||||
|
||||
// Dispatch another key event
|
||||
@ -532,8 +607,8 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithAsyncResponds) {
|
||||
EXPECT_EQ(manager_handled, true);
|
||||
EXPECT_EQ(redispatched.size(), 0u);
|
||||
EXPECT_EQ(call_records.size(), 2u);
|
||||
EXPECT_KEY_EVENT(call_records[1].event, kFlutterKeyEventTypeDown,
|
||||
kPhysicalKeyB, kLogicalKeyB, "b", false);
|
||||
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);
|
||||
@ -584,8 +659,8 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) {
|
||||
tester.flushChannelMessages();
|
||||
EXPECT_EQ(manager_handled, true);
|
||||
EXPECT_EQ(call_records.size(), 1u);
|
||||
EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeDown,
|
||||
kPhysicalKeyA, kLogicalKeyA, "a", false);
|
||||
EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeDown, kPhysicalKeyA,
|
||||
kLogicalKeyA, "a", false);
|
||||
EXPECT_EQ(redispatched.size(), 0u);
|
||||
call_records.clear();
|
||||
|
||||
@ -600,7 +675,7 @@ TEST(FlKeyboardManagerTest, SingleDelegateWithSyncResponds) {
|
||||
tester.flushChannelMessages();
|
||||
EXPECT_EQ(manager_handled, true);
|
||||
EXPECT_EQ(call_records.size(), 1u);
|
||||
EXPECT_KEY_EVENT(call_records[0].event, kFlutterKeyEventTypeUp, kPhysicalKeyA,
|
||||
EXPECT_KEY_EVENT(call_records[0], kFlutterKeyEventTypeUp, kPhysicalKeyA,
|
||||
kLogicalKeyA, nullptr, false);
|
||||
EXPECT_EQ(redispatched.size(), 1u);
|
||||
call_records.clear();
|
||||
@ -712,4 +787,285 @@ TEST(FlKeyboardManagerTest, TextInputPluginReturnsTrue) {
|
||||
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, "[");
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
@ -48,3 +48,19 @@ void fl_keyboard_view_delegate_redispatch_event(
|
||||
return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->redispatch_event(
|
||||
self, std::move(event));
|
||||
}
|
||||
|
||||
void fl_keyboard_view_delegate_subscribe_to_layout_change(
|
||||
FlKeyboardViewDelegate* self,
|
||||
KeyboardLayoutNotifier notifier) {
|
||||
g_return_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self));
|
||||
|
||||
return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->subscribe_to_layout_change(
|
||||
self, std::move(notifier));
|
||||
}
|
||||
|
||||
guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* self,
|
||||
const GdkKeymapKey* key) {
|
||||
g_return_val_if_fail(FL_IS_KEYBOARD_VIEW_DELEGATE(self), 0);
|
||||
|
||||
return FL_KEYBOARD_VIEW_DELEGATE_GET_IFACE(self)->lookup_key(self, key);
|
||||
}
|
||||
|
||||
@ -7,12 +7,15 @@
|
||||
|
||||
#include <gdk/gdk.h>
|
||||
#include <cinttypes>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "flutter/shell/platform/embedder/embedder.h"
|
||||
#include "flutter/shell/platform/linux/fl_key_event.h"
|
||||
#include "flutter/shell/platform/linux/public/flutter_linux/fl_binary_messenger.h"
|
||||
|
||||
typedef std::function<void()> KeyboardLayoutNotifier;
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
G_DECLARE_INTERFACE(FlKeyboardViewDelegate,
|
||||
@ -45,6 +48,12 @@ struct _FlKeyboardViewDelegateInterface {
|
||||
|
||||
void (*redispatch_event)(FlKeyboardViewDelegate* delegate,
|
||||
std::unique_ptr<FlKeyEvent> event);
|
||||
|
||||
void (*subscribe_to_layout_change)(FlKeyboardViewDelegate* delegate,
|
||||
KeyboardLayoutNotifier notifier);
|
||||
|
||||
guint (*lookup_key)(FlKeyboardViewDelegate* view_delegate,
|
||||
const GdkKeymapKey* key);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -100,6 +109,13 @@ void fl_keyboard_view_delegate_redispatch_event(
|
||||
FlKeyboardViewDelegate* delegate,
|
||||
std::unique_ptr<FlKeyEvent> event);
|
||||
|
||||
void fl_keyboard_view_delegate_subscribe_to_layout_change(
|
||||
FlKeyboardViewDelegate* delegate,
|
||||
KeyboardLayoutNotifier notifier);
|
||||
|
||||
guint fl_keyboard_view_delegate_lookup_key(FlKeyboardViewDelegate* delegate,
|
||||
const GdkKeymapKey* key);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_VIEW_DELEGATE_H_
|
||||
|
||||
@ -56,6 +56,10 @@ struct _FlView {
|
||||
|
||||
// Tracks whether mouse pointer is inside the view.
|
||||
gboolean pointer_inside;
|
||||
|
||||
/* FlKeyboardViewDelegate related properties */
|
||||
KeyboardLayoutNotifier keyboard_layout_notifier;
|
||||
GdkKeymap* keymap;
|
||||
};
|
||||
|
||||
typedef struct _FlViewChild {
|
||||
@ -250,15 +254,18 @@ static void fl_view_keyboard_delegate_iface_init(
|
||||
fl_engine_send_key_event(self->engine, event, callback, user_data);
|
||||
};
|
||||
};
|
||||
|
||||
iface->text_filter_key_press = [](FlKeyboardViewDelegate* view_delegate,
|
||||
FlKeyEvent* event) {
|
||||
FlView* self = FL_VIEW(view_delegate);
|
||||
return fl_text_input_plugin_filter_keypress(self->text_input_plugin, event);
|
||||
};
|
||||
|
||||
iface->get_messenger = [](FlKeyboardViewDelegate* view_delegate) {
|
||||
FlView* self = FL_VIEW(view_delegate);
|
||||
return fl_engine_get_binary_messenger(self->engine);
|
||||
};
|
||||
|
||||
iface->redispatch_event = [](FlKeyboardViewDelegate* view_delegate,
|
||||
std::unique_ptr<FlKeyEvent> in_event) {
|
||||
FlKeyEvent* event = in_event.release();
|
||||
@ -268,6 +275,18 @@ static void fl_view_keyboard_delegate_iface_init(
|
||||
gdk_event_put(gdk_event);
|
||||
fl_key_event_dispose(event);
|
||||
};
|
||||
|
||||
iface->subscribe_to_layout_change = [](FlKeyboardViewDelegate* view_delegate,
|
||||
KeyboardLayoutNotifier notifier) {
|
||||
FlView* self = FL_VIEW(view_delegate);
|
||||
self->keyboard_layout_notifier = std::move(notifier);
|
||||
};
|
||||
|
||||
iface->lookup_key = [](FlKeyboardViewDelegate* view_delegate,
|
||||
const GdkKeymapKey* key) -> guint {
|
||||
FlView* self = FL_VIEW(view_delegate);
|
||||
return gdk_keymap_lookup_key(self->keymap, key);
|
||||
};
|
||||
}
|
||||
|
||||
// Signal handler for GtkWidget::button-press-event
|
||||
@ -386,6 +405,14 @@ static gboolean leave_notify_event_cb(GtkWidget* widget,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void keymap_keys_changed_cb(GdkKeymap* self, FlView* view) {
|
||||
if (view->keyboard_layout_notifier == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
view->keyboard_layout_notifier();
|
||||
}
|
||||
|
||||
static void fl_view_constructed(GObject* object) {
|
||||
FlView* self = FL_VIEW(object);
|
||||
|
||||
@ -396,6 +423,9 @@ static void fl_view_constructed(GObject* object) {
|
||||
fl_engine_set_on_pre_engine_restart_handler(
|
||||
self->engine, on_pre_engine_restart_cb, self, nullptr);
|
||||
|
||||
// Must initialize the keymap before the keyboard.
|
||||
self->keymap = gdk_keymap_get_for_display(gdk_display_get_default());
|
||||
|
||||
// Create system channel handlers.
|
||||
FlBinaryMessenger* messenger = fl_engine_get_binary_messenger(self->engine);
|
||||
self->accessibility_plugin = fl_accessibility_plugin_new(self);
|
||||
@ -423,6 +453,8 @@ static void fl_view_constructed(GObject* object) {
|
||||
G_CALLBACK(enter_notify_event_cb), self);
|
||||
g_signal_connect(self->event_box, "leave-notify-event",
|
||||
G_CALLBACK(leave_notify_event_cb), self);
|
||||
g_signal_connect(self->keymap, "keys-changed",
|
||||
G_CALLBACK(keymap_keys_changed_cb), self);
|
||||
}
|
||||
|
||||
static void fl_view_set_property(GObject* object,
|
||||
@ -710,6 +742,8 @@ static void fl_view_remove(GtkContainer* container, GtkWidget* widget) {
|
||||
if (widget == GTK_WIDGET(self->event_box)) {
|
||||
g_clear_object(&self->event_box);
|
||||
}
|
||||
|
||||
g_clear_object(&self->keymap);
|
||||
}
|
||||
|
||||
// Implements GtkContainer::forall
|
||||
|
||||
@ -207,6 +207,7 @@ std::map<uint64_t, uint64_t> xkb_to_physical_key_map = {
|
||||
{0x000000f2, 0x000c0207}, // save
|
||||
{0x000000f3, 0x000c01a7}, // launchDocuments
|
||||
{0x000000fc, 0x000c0075}, // brightnessAuto
|
||||
{0x00000100, 0x00000018}, // microphoneMuteToggle
|
||||
{0x0000016e, 0x000c0060}, // info
|
||||
{0x00000172, 0x000c008d}, // programGuide
|
||||
{0x0000017a, 0x000c0061}, // closedCaptionToggle
|
||||
@ -458,6 +459,57 @@ void initialize_lock_bit_to_checked_keys(GHashTable* table) {
|
||||
data->primary_logical_key = 0x0010000010a; // numLock
|
||||
}
|
||||
|
||||
const std::vector<LayoutGoal> layout_goals = {
|
||||
LayoutGoal{0x30, 0x22, false}, // Quote
|
||||
LayoutGoal{0x3b, 0x2c, false}, // Comma
|
||||
LayoutGoal{0x14, 0x2d, false}, // Minus
|
||||
LayoutGoal{0x3c, 0x2e, false}, // Period
|
||||
LayoutGoal{0x3d, 0x2f, false}, // Slash
|
||||
LayoutGoal{0x13, 0x30, true}, // Digit0
|
||||
LayoutGoal{0x0a, 0x31, true}, // Digit1
|
||||
LayoutGoal{0x0b, 0x32, true}, // Digit2
|
||||
LayoutGoal{0x0c, 0x33, true}, // Digit3
|
||||
LayoutGoal{0x0d, 0x34, true}, // Digit4
|
||||
LayoutGoal{0x0e, 0x35, true}, // Digit5
|
||||
LayoutGoal{0x0f, 0x36, true}, // Digit6
|
||||
LayoutGoal{0x10, 0x37, true}, // Digit7
|
||||
LayoutGoal{0x11, 0x38, true}, // Digit8
|
||||
LayoutGoal{0x12, 0x39, true}, // Digit9
|
||||
LayoutGoal{0x2f, 0x3b, false}, // Semicolon
|
||||
LayoutGoal{0x15, 0x3d, false}, // Equal
|
||||
LayoutGoal{0x22, 0x5b, false}, // BracketLeft
|
||||
LayoutGoal{0x33, 0x5c, false}, // Backslash
|
||||
LayoutGoal{0x23, 0x5d, false}, // BracketRight
|
||||
LayoutGoal{0x31, 0x60, false}, // Backquote
|
||||
LayoutGoal{0x26, 0x61, true}, // KeyA
|
||||
LayoutGoal{0x38, 0x62, true}, // KeyB
|
||||
LayoutGoal{0x36, 0x63, true}, // KeyC
|
||||
LayoutGoal{0x28, 0x64, true}, // KeyD
|
||||
LayoutGoal{0x1a, 0x65, true}, // KeyE
|
||||
LayoutGoal{0x29, 0x66, true}, // KeyF
|
||||
LayoutGoal{0x2a, 0x67, true}, // KeyG
|
||||
LayoutGoal{0x2b, 0x68, true}, // KeyH
|
||||
LayoutGoal{0x1f, 0x69, true}, // KeyI
|
||||
LayoutGoal{0x2c, 0x6a, true}, // KeyJ
|
||||
LayoutGoal{0x2d, 0x6b, true}, // KeyK
|
||||
LayoutGoal{0x2e, 0x6c, true}, // KeyL
|
||||
LayoutGoal{0x3a, 0x6d, true}, // KeyM
|
||||
LayoutGoal{0x39, 0x6e, true}, // KeyN
|
||||
LayoutGoal{0x20, 0x6f, true}, // KeyO
|
||||
LayoutGoal{0x21, 0x70, true}, // KeyP
|
||||
LayoutGoal{0x18, 0x71, true}, // KeyQ
|
||||
LayoutGoal{0x1b, 0x72, true}, // KeyR
|
||||
LayoutGoal{0x27, 0x73, true}, // KeyS
|
||||
LayoutGoal{0x1c, 0x74, true}, // KeyT
|
||||
LayoutGoal{0x1e, 0x75, true}, // KeyU
|
||||
LayoutGoal{0x37, 0x76, true}, // KeyV
|
||||
LayoutGoal{0x19, 0x77, true}, // KeyW
|
||||
LayoutGoal{0x35, 0x78, true}, // KeyX
|
||||
LayoutGoal{0x1d, 0x79, true}, // KeyY
|
||||
LayoutGoal{0x34, 0x7a, true}, // KeyZ
|
||||
LayoutGoal{0x5e, 0x200000020, false}, // IntlBackslash
|
||||
};
|
||||
|
||||
const uint64_t kValueMask = 0x000ffffffff;
|
||||
const uint64_t kUnicodePlane = 0x00000000000;
|
||||
const uint64_t kGtkPlane = 0x01500000000;
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <gdk/gdk.h>
|
||||
#include <cinttypes>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
inline uint64_t gpointer_to_uint64(gpointer pointer) {
|
||||
return pointer == nullptr ? 0 : reinterpret_cast<uint64_t>(pointer);
|
||||
@ -36,4 +37,18 @@ extern const uint64_t kUnicodePlane;
|
||||
// The plane value for the private keys defined by the GTK embedding.
|
||||
extern const uint64_t kGtkPlane;
|
||||
|
||||
typedef struct {
|
||||
// The key code for a key that prints `keyChar` in the US keyboard layout.
|
||||
uint16_t keycode;
|
||||
|
||||
// The logical key for this key.
|
||||
uint64_t logical_key;
|
||||
|
||||
// If the goal is mandatory, the keyboard manager will make sure to find a
|
||||
// logical key for this character, falling back to the US keyboard layout.
|
||||
bool mandatory;
|
||||
} LayoutGoal;
|
||||
|
||||
extern const std::vector<LayoutGoal> layout_goals;
|
||||
|
||||
#endif // KEYBOARD_MAP_H_
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user