Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit a1a511a

Browse files
committed
Split Linux (GTK) out
1 parent 550c23f commit a1a511a

File tree

10 files changed

+783
-2
lines changed

10 files changed

+783
-2
lines changed

shell/platform/linux/BUILD.gn

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ source_set("flutter_linux_sources") {
7979
"fl_method_codec_private.h",
8080
"fl_plugin_registrar_private.h",
8181
"fl_standard_message_codec_private.h",
82+
"keyboard_map_private.h",
8283
]
8384

8485
configs += [
@@ -97,6 +98,7 @@ source_set("flutter_linux_sources") {
9798
"fl_json_message_codec.cc",
9899
"fl_json_method_codec.cc",
99100
"fl_key_event_plugin.cc",
101+
"fl_keyboard_manager.cc",
100102
"fl_message_codec.cc",
101103
"fl_method_call.cc",
102104
"fl_method_channel.cc",
@@ -117,6 +119,7 @@ source_set("flutter_linux_sources") {
117119
"fl_text_input_plugin.cc",
118120
"fl_value.cc",
119121
"fl_view.cc",
122+
"keyboard_map.cc",
120123
]
121124

122125
# Set flag to stop headers being directly included (library users should not do this)
@@ -167,6 +170,7 @@ executable("flutter_linux_unittests") {
167170
"fl_json_message_codec_test.cc",
168171
"fl_json_method_codec_test.cc",
169172
"fl_key_event_plugin_test.cc",
173+
"fl_keyboard_manager_test.cc",
170174
"fl_message_codec_test.cc",
171175
"fl_method_channel_test.cc",
172176
"fl_method_codec_test.cc",

shell/platform/linux/fl_engine.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,11 @@ void fl_engine_send_mouse_pointer_event(FlEngine* self,
613613
self->embedder_api.SendPointerEvent(self->engine, &fl_event, 1);
614614
}
615615

616+
void fl_engine_send_key_event(FlEngine* self,
617+
const FlutterKeyEvent* event) {
618+
self->embedder_api.SendKeyEvent(self->engine, event);
619+
}
620+
616621
G_MODULE_EXPORT FlBinaryMessenger* fl_engine_get_binary_messenger(
617622
FlEngine* self) {
618623
g_return_val_if_fail(FL_IS_ENGINE(self), nullptr);

shell/platform/linux/fl_engine_private.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,16 @@ void fl_engine_send_mouse_pointer_event(FlEngine* engine,
133133
double scroll_delta_y,
134134
int64_t buttons);
135135

136+
/**
137+
* fl_engine_send_key_event:
138+
* @engine: an #FlEngine.
139+
* @event: the key event to send.
140+
*
141+
* Sends a key event to the engine.
142+
*/
143+
void fl_engine_send_key_event(FlEngine* engine,
144+
const FlutterKeyEvent* event);
145+
136146
/**
137147
* fl_engine_send_platform_message_response:
138148
* @engine: an #FlEngine.
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
6+
7+
#include <gtk/gtk.h>
8+
#include <glib.h>
9+
10+
#include "flutter/shell/platform/linux/keyboard_map_private.h"
11+
12+
// Ensures that a uint64_t can be safely fit into a gpointer.
13+
//
14+
// Flutter keyboard keys, values of uint64_t, are used by GHashTable by being
15+
// reinterpreted as gpointers. This greatly simplifies the implementation
16+
// (otherwise all keys and values needs allocation), but requires pointers to
17+
// be at least 64-bit long.
18+
//
19+
// Reinterpreting integers as gpointers for GHashTable is officially supported
20+
// by GTK, but natively it only supports 32-bit, probably due to crossplatform.
21+
static_assert(sizeof(gpointer) >= sizeof(uint64_t));
22+
23+
static constexpr uint64_t kLinuxKeyIdPlane = 0x00400000000;
24+
static constexpr uint64_t kAutogeneratedMask = 0x10000000000;
25+
// // The `gdk_keyval_to_unicode` returns a guint32 character, which should not
26+
// // exceed this length (including \0) after being converted to UTF-8.
27+
// static constexpr size_t kCharacterPoolSize = 5;
28+
static constexpr double kMicrosecondsPerMillisecond = 1000;
29+
30+
struct _FlKeyboardManager {
31+
gchar* character_to_free;
32+
33+
GObject parent_instance;
34+
35+
// Stores pressed keys, mapping their Flutter physical key to Flutter logical key.
36+
//
37+
// Both keys and values are directly stored uint64s.
38+
GHashTable* pressing_records;
39+
40+
// A static map from XKB to Flutter's physical key code
41+
GHashTable* xkb_to_physical_key;
42+
43+
// A static map from GTK keyval to Flutter's logical key code
44+
GHashTable* keyval_to_logical_key;
45+
};
46+
47+
G_DEFINE_TYPE(FlKeyboardManager, fl_keyboard_manager, G_TYPE_OBJECT)
48+
49+
static uint64_t event_to_physical_key(const GdkEventKey* event, GHashTable* table) {
50+
gpointer record = g_hash_table_lookup(table, GUINT_TO_POINTER(event->hardware_keycode));
51+
if (record != nullptr) {
52+
return GPOINTER_TO_UINT(record);
53+
}
54+
// Auto-generate key
55+
return kAutogeneratedMask | kLinuxKeyIdPlane | event->hardware_keycode;
56+
}
57+
58+
static uint64_t event_to_logical_key(const GdkEventKey* event, GHashTable* table) {
59+
guint keyval = event->keyval;
60+
gpointer record = g_hash_table_lookup(table, GUINT_TO_POINTER(keyval));
61+
if (record != nullptr) {
62+
return GPOINTER_TO_UINT(record);
63+
}
64+
// ASCII // TODO
65+
if (keyval < 256) {
66+
return keyval;
67+
}
68+
// Auto-generate key
69+
return kAutogeneratedMask | kLinuxKeyIdPlane | keyval;
70+
}
71+
72+
static uint64_t event_to_timestamp(const GdkEventKey* event) {
73+
return kMicrosecondsPerMillisecond * (double)event->time;
74+
}
75+
76+
// Returns a newly accocated UTF-8 string from event->keyval that must be
77+
// freed later with g_free().
78+
static char* event_to_character(const GdkEventKey* event) {
79+
gunichar unicodeChar = gdk_keyval_to_unicode(event->keyval);
80+
glong items_written;
81+
gchar* result = g_ucs4_to_utf8(&unicodeChar, 1, NULL, &items_written, NULL);
82+
if (items_written == 0) {
83+
if (result != NULL)
84+
g_free(result);
85+
return nullptr;
86+
}
87+
return result;
88+
}
89+
90+
static uint64_t gpointerToUint64(gpointer pointer) {
91+
return pointer == nullptr ? 0 : reinterpret_cast<uint64_t>(pointer);
92+
}
93+
94+
static gpointer uint64ToGpointer(uint64_t number) {
95+
return reinterpret_cast<gpointer>(number);
96+
}
97+
98+
// Return the logical key corresponding to the physical key.
99+
//
100+
// Returns 0 if not found.
101+
static uint64_t pressed_logical_for_physical(GHashTable* pressing_records, uint64_t physical) {
102+
return gpointerToUint64(g_hash_table_lookup(pressing_records, uint64ToGpointer(physical)));
103+
}
104+
105+
size_t fl_keyboard_manager_convert_key_event(FlKeyboardManager* self,
106+
const GdkEventKey* event,
107+
FlKeyDatum* results) {
108+
printf("===START=== state %d\n", event->state);
109+
if (self->character_to_free != nullptr) {
110+
g_free(self->character_to_free);
111+
self->character_to_free = nullptr;
112+
}
113+
uint64_t physical_key = event_to_physical_key(event, self->xkb_to_physical_key);
114+
uint64_t logical_key = event_to_logical_key(event, self->keyval_to_logical_key);
115+
bool is_physical_down = event->type == GDK_KEY_PRESS;
116+
117+
uint64_t last_logical_record = pressed_logical_for_physical(self->pressing_records, physical_key);
118+
uint64_t next_logical_record = is_physical_down ? last_logical_record : 0;
119+
120+
char* character_to_free = nullptr;
121+
122+
printf("last %lu next %lu down %d type %d\n", last_logical_record, next_logical_record, is_physical_down, event->type);
123+
fflush(stdout);
124+
125+
FlKeyDatum* base_event = results + 0;
126+
base_event->physical = physical_key;
127+
base_event->timestamp = event_to_timestamp(event);
128+
base_event->synthesized = false;
129+
130+
if (is_physical_down) {
131+
character_to_free = event_to_character(event); // Might be null
132+
base_event->character = character_to_free;
133+
134+
if (last_logical_record) {
135+
// GTK doesn't report repeat events separatedly, therefore we can't
136+
// distinguish a repeat event from a down event after a missed up event.
137+
base_event->kind = kFlKeyDataKindRepeat;
138+
base_event->logical = last_logical_record;
139+
} else {
140+
base_event->kind = kFlKeyDataKindDown;
141+
base_event->logical = logical_key;
142+
}
143+
} else { // is_physical_down false
144+
base_event->character = nullptr;
145+
base_event->kind = kFlKeyDataKindUp;
146+
base_event->logical = logical_key;
147+
}
148+
149+
return 1;
150+
}
151+
152+
static void fl_keyboard_manager_dispose(GObject* object) {
153+
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(object);
154+
155+
g_clear_pointer(&self->pressing_records, g_hash_table_unref);
156+
g_clear_pointer(&self->xkb_to_physical_key, g_hash_table_unref);
157+
g_clear_pointer(&self->keyval_to_logical_key, g_hash_table_unref);
158+
if (self->character_to_free != nullptr) {
159+
g_free(self->character_to_free);
160+
}
161+
162+
G_OBJECT_CLASS(fl_keyboard_manager_parent_class)->dispose(object);
163+
}
164+
165+
static void fl_keyboard_manager_class_init(FlKeyboardManagerClass* klass) {
166+
G_OBJECT_CLASS(klass)->dispose = fl_keyboard_manager_dispose;
167+
}
168+
169+
static void fl_keyboard_manager_init(FlKeyboardManager* self) {
170+
}
171+
172+
FlKeyboardManager* fl_keyboard_manager_new() {
173+
FlKeyboardManager* self = FL_KEYBOARD_MANAGER(
174+
g_object_new(fl_keyboard_manager_get_type(), nullptr));
175+
176+
self->pressing_records = g_hash_table_new(g_direct_hash, g_direct_equal);
177+
self->xkb_to_physical_key = g_hash_table_new(g_direct_hash, g_direct_equal);
178+
initialize_xkb_to_physical_key(self->xkb_to_physical_key);
179+
self->keyval_to_logical_key = g_hash_table_new(g_direct_hash, g_direct_equal);
180+
initialize_gtk_keyval_to_logical_key(self->keyval_to_logical_key);
181+
self->character_to_free = nullptr;
182+
183+
return self;
184+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
6+
#define FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
7+
8+
#include <gdk/gdk.h>
9+
#include <stdint.h>
10+
11+
#include "flutter/shell/platform/linux/public/flutter_linux/fl_view.h"
12+
13+
typedef enum {
14+
kFlKeyDataKindDown = 0,
15+
kFlKeyDataKindUp,
16+
kFlKeyDataKindRepeat,
17+
} FlKeyEventKind;
18+
19+
typedef struct {
20+
FlKeyEventKind kind;
21+
double timestamp;
22+
uint64_t physical;
23+
uint64_t logical;
24+
const char* character;
25+
bool synthesized;
26+
} FlKeyDatum;
27+
28+
constexpr int kMaxConvertedKeyData = 3;
29+
30+
G_BEGIN_DECLS
31+
32+
G_DECLARE_FINAL_TYPE(FlKeyboardManager,
33+
fl_keyboard_manager,
34+
FL,
35+
KEYBOARD_MANAGER,
36+
GObject);
37+
38+
FlKeyboardManager* fl_keyboard_manager_new();
39+
40+
size_t fl_keyboard_manager_convert_key_event(FlKeyboardManager* self,
41+
const GdkEventKey* event,
42+
FlKeyDatum* results);
43+
44+
G_END_DECLS
45+
46+
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_KEYBOARD_MANAGER_H_
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/shell/platform/linux/fl_keyboard_manager.h"
6+
7+
#include <iostream>
8+
#include "gtest/gtest.h"
9+
10+
#include "flutter/shell/platform/linux/fl_engine_private.h"
11+
#include "flutter/shell/platform/linux/testing/fl_test.h"
12+
#include "flutter/shell/platform/linux/testing/mock_renderer.h"
13+
14+
static void expect_key_datum_eq(const FlKeyDatum& data, const FlKeyDatum& target) {
15+
EXPECT_EQ(data.kind, target.kind);
16+
EXPECT_EQ(data.physical, target.physical);
17+
EXPECT_EQ(data.logical, target.logical);
18+
EXPECT_EQ(data.timestamp, target.timestamp);
19+
EXPECT_STREQ(data.character, target.character);
20+
EXPECT_EQ(data.synthesized, target.synthesized);
21+
}
22+
23+
// Test sending a letter "a";
24+
TEST(FlKeyboardManagerTest, SendKeyEvent) {
25+
g_autoptr(FlKeyboardManager) manager = fl_keyboard_manager_new();
26+
FlKeyDatum physical_data[kMaxConvertedKeyData];
27+
size_t result_size;
28+
29+
char string[] = "a";
30+
GdkEventKey key_event = GdkEventKey{
31+
GDK_KEY_PRESS, // event type
32+
nullptr, // window (not needed)
33+
FALSE, // event was sent explicitly
34+
12345, // time
35+
0x0, // modifier state
36+
GDK_KEY_a, // key code
37+
1, // length of string representation
38+
reinterpret_cast<gchar*>(&string[0]), // string representation
39+
0x026, // scan code
40+
0, // keyboard group
41+
0, // is a modifier
42+
};
43+
44+
result_size = fl_keyboard_manager_convert_key_event(manager, &key_event, physical_data);
45+
46+
EXPECT_EQ(result_size, 1u);
47+
expect_key_datum_eq(physical_data[0], FlKeyDatum{
48+
kFlKeyDataKindDown, // kind
49+
12345000.0, // timestamp
50+
0x00070004, // physical
51+
0x00000061, // logical
52+
"a", // character
53+
false, // synthesized
54+
});
55+
}

0 commit comments

Comments
 (0)