[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:
Tong Mu 2022-04-21 00:48:22 -07:00 committed by GitHub
parent bcd33ce3a6
commit d5820083a5
18 changed files with 848 additions and 66 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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).

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -39,6 +39,8 @@ typedef struct _FlKeyEvent {
guint keyval;
// Modifier state.
int state;
// Keyboard group.
guint8 group;
// String, null-terminated.
//
// Can be nullptr.

View File

@ -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);
}

View File

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

View File

@ -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);

View File

@ -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"

View File

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

View File

@ -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);
}

View File

@ -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_

View File

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

View File

@ -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;

View File

@ -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_