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

Commit bdadaad

Browse files
authored
Add delayed event delivery for Linux. (#22577)
This changes the text handling so that keyboard events are sent to the framework first for handling, and then passed to the text input plugin, so that the framework has a chance to handle keys before they get given to the text field. This is complicated by the async nature of the interaction with the framework, since GTK wants a synchronous response. So, in this change, I always tell GTK that the event was handled, and if it wasn't, then I re-dispatch the event once we know one way or the other.
1 parent 66f44c6 commit bdadaad

File tree

10 files changed

+735
-180
lines changed

10 files changed

+735
-180
lines changed

shell/platform/linux/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ executable("flutter_linux_unittests") {
177177
"testing/mock_egl.cc",
178178
"testing/mock_engine.cc",
179179
"testing/mock_renderer.cc",
180+
"testing/mock_text_input_plugin.cc",
180181
]
181182

182183
public_configs = [ "//flutter:config" ]

shell/platform/linux/fl_key_event_plugin.cc

Lines changed: 285 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
// found in the LICENSE file.
44

55
#include "flutter/shell/platform/linux/fl_key_event_plugin.h"
6+
7+
#include <gtk/gtk.h>
8+
9+
#include "flutter/shell/platform/linux/fl_text_input_plugin.h"
610
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
711
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
812

@@ -20,33 +24,272 @@ static constexpr char kUnicodeScalarValuesKey[] = "unicodeScalarValues";
2024
static constexpr char kGtkToolkit[] = "gtk";
2125
static constexpr char kLinuxKeymap[] = "linux";
2226

27+
static constexpr uint64_t kMaxPendingEvents = 1000;
28+
29+
// Definition of the FlKeyEventPlugin GObject class.
30+
2331
struct _FlKeyEventPlugin {
2432
GObject parent_instance;
2533

2634
FlBasicMessageChannel* channel = nullptr;
27-
GAsyncReadyCallback response_callback = nullptr;
35+
FlTextInputPlugin* text_input_plugin = nullptr;
36+
FlKeyEventPluginCallback response_callback = nullptr;
37+
GPtrArray* pending_events;
2838
};
2939

3040
G_DEFINE_TYPE(FlKeyEventPlugin, fl_key_event_plugin, G_TYPE_OBJECT)
3141

42+
// Declare and define a private pair object to bind the id and the event
43+
// together.
44+
45+
G_DECLARE_FINAL_TYPE(FlKeyEventPair,
46+
fl_key_event_pair,
47+
FL,
48+
KEY_EVENT_PAIR,
49+
GObject);
50+
51+
struct _FlKeyEventPair {
52+
GObject parent_instance;
53+
54+
uint64_t id;
55+
GdkEventKey* event;
56+
};
57+
58+
G_DEFINE_TYPE(FlKeyEventPair, fl_key_event_pair, G_TYPE_OBJECT)
59+
60+
// Dispose method for FlKeyEventPair.
61+
static void fl_key_event_pair_dispose(GObject* object) {
62+
// Redundant, but added so that we don't get a warning about unused function
63+
// for FL_IS_KEY_EVENT_PAIR.
64+
g_return_if_fail(FL_IS_KEY_EVENT_PAIR(object));
65+
66+
FlKeyEventPair* self = FL_KEY_EVENT_PAIR(object);
67+
g_clear_pointer(&self->event, gdk_event_free);
68+
G_OBJECT_CLASS(fl_key_event_pair_parent_class)->dispose(object);
69+
}
70+
71+
// Class Initialization method for FlKeyEventPair class.
72+
static void fl_key_event_pair_class_init(FlKeyEventPairClass* klass) {
73+
G_OBJECT_CLASS(klass)->dispose = fl_key_event_pair_dispose;
74+
}
75+
76+
// Initialization for FlKeyEventPair instances.
77+
static void fl_key_event_pair_init(FlKeyEventPair* self) {}
78+
79+
// Creates a new FlKeyEventPair instance, given a unique ID, and an event struct
80+
// to keep.
81+
FlKeyEventPair* fl_key_event_pair_new(uint64_t id, GdkEventKey* event) {
82+
FlKeyEventPair* self =
83+
FL_KEY_EVENT_PAIR(g_object_new(fl_key_event_pair_get_type(), nullptr));
84+
85+
// Copy the event to preserve refcounts for referenced values (mainly the
86+
// window).
87+
GdkEventKey* event_copy = reinterpret_cast<GdkEventKey*>(
88+
gdk_event_copy(reinterpret_cast<GdkEvent*>(event)));
89+
self->id = id;
90+
self->event = event_copy;
91+
return self;
92+
}
93+
94+
// Declare and define a private class to hold response data from the framework.
95+
G_DECLARE_FINAL_TYPE(FlKeyEventResponseData,
96+
fl_key_event_response_data,
97+
FL,
98+
KEY_EVENT_RESPONSE_DATA,
99+
GObject);
100+
101+
struct _FlKeyEventResponseData {
102+
GObject parent_instance;
103+
104+
FlKeyEventPlugin* plugin;
105+
uint64_t id;
106+
gpointer user_data;
107+
};
108+
109+
// Definition for FlKeyEventResponseData private class.
110+
G_DEFINE_TYPE(FlKeyEventResponseData, fl_key_event_response_data, G_TYPE_OBJECT)
111+
112+
// Dispose method for FlKeyEventResponseData private class.
113+
static void fl_key_event_response_data_dispose(GObject* object) {
114+
g_return_if_fail(FL_IS_KEY_EVENT_RESPONSE_DATA(object));
115+
FlKeyEventResponseData* self = FL_KEY_EVENT_RESPONSE_DATA(object);
116+
// Don't need to weak pointer anymore.
117+
g_object_remove_weak_pointer(G_OBJECT(self->plugin),
118+
reinterpret_cast<gpointer*>(&(self->plugin)));
119+
}
120+
121+
// Class initialization method for FlKeyEventResponseData private class.
122+
static void fl_key_event_response_data_class_init(
123+
FlKeyEventResponseDataClass* klass) {
124+
G_OBJECT_CLASS(klass)->dispose = fl_key_event_response_data_dispose;
125+
}
126+
127+
// Instance initialization method for FlKeyEventResponseData private class.
128+
static void fl_key_event_response_data_init(FlKeyEventResponseData* self) {}
129+
130+
// Creates a new FlKeyEventResponseData private class with a plugin that created
131+
// the request, a unique ID for tracking, and optional user data.
132+
// Will keep a weak pointer to the plugin.
133+
FlKeyEventResponseData* fl_key_event_response_data_new(FlKeyEventPlugin* plugin,
134+
uint64_t id,
135+
gpointer user_data) {
136+
FlKeyEventResponseData* self = FL_KEY_EVENT_RESPONSE_DATA(
137+
g_object_new(fl_key_event_response_data_get_type(), nullptr));
138+
139+
self->plugin = plugin;
140+
// Add a weak pointer so we can know if the key event plugin disappeared
141+
// while the framework was responding.
142+
g_object_add_weak_pointer(G_OBJECT(plugin),
143+
reinterpret_cast<gpointer*>(&(self->plugin)));
144+
self->id = id;
145+
self->user_data = user_data;
146+
return self;
147+
}
148+
149+
// Calculates a unique ID for a given GdkEventKey object to use for
150+
// identification of responses from the framework.
151+
static uint64_t get_event_id(GdkEventKey* event) {
152+
// Combine the event timestamp, the type of event, and the hardware keycode
153+
// (scan code) of the event to come up with a unique id for this event that
154+
// can be derived solely from the event data itself, so that we can identify
155+
// whether or not we have seen this event already.
156+
return (event->time & 0xffffffff) |
157+
(static_cast<uint64_t>(event->type) & 0xffff) << 32 |
158+
(static_cast<uint64_t>(event->hardware_keycode) & 0xffff) << 48;
159+
}
160+
161+
// Finds an event in the event queue that was sent to the framework by its ID.
162+
static GdkEventKey* find_pending_event(FlKeyEventPlugin* self, uint64_t id) {
163+
if (self->pending_events->len == 0 ||
164+
FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id != id) {
165+
return nullptr;
166+
}
167+
168+
return FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->event;
169+
}
170+
171+
// Removes an event from the pending event queue.
172+
static void remove_pending_event(FlKeyEventPlugin* self, uint64_t id) {
173+
if (self->pending_events->len == 0 ||
174+
FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id != id) {
175+
g_warning(
176+
"Tried to remove pending event with id %ld, but the event was out of "
177+
"order, or is unknown.",
178+
id);
179+
return;
180+
}
181+
g_ptr_array_remove_index(self->pending_events, 0);
182+
}
183+
184+
// Adds an GdkEventKey to the pending event queue, with a unique ID, and the
185+
// plugin that added it.
186+
static void add_pending_event(FlKeyEventPlugin* self,
187+
uint64_t id,
188+
GdkEventKey* event) {
189+
if (self->pending_events->len > kMaxPendingEvents) {
190+
g_warning(
191+
"There are %d keyboard events that have not yet received a "
192+
"response from the framework. Are responses being sent?",
193+
self->pending_events->len);
194+
}
195+
g_ptr_array_add(self->pending_events, fl_key_event_pair_new(id, event));
196+
}
197+
198+
// Handles a response from the framework to a key event sent to the framework
199+
// earlier.
200+
static void handle_response(GObject* object,
201+
GAsyncResult* result,
202+
gpointer user_data) {
203+
g_autoptr(FlKeyEventResponseData) data =
204+
FL_KEY_EVENT_RESPONSE_DATA(user_data);
205+
206+
// Will also return if the weak pointer has been destroyed.
207+
if (data->plugin == nullptr) {
208+
return;
209+
}
210+
211+
FlKeyEventPlugin* self = data->plugin;
212+
213+
g_autoptr(GError) error = nullptr;
214+
FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object);
215+
FlValue* message =
216+
fl_basic_message_channel_send_finish(messageChannel, result, &error);
217+
if (error != nullptr) {
218+
g_warning("Unable to retrieve framework response: %s", error->message);
219+
return;
220+
}
221+
g_autoptr(FlValue) handled_value = fl_value_lookup_string(message, "handled");
222+
bool handled = FALSE;
223+
if (handled_value != nullptr) {
224+
GdkEventKey* event = find_pending_event(self, data->id);
225+
if (event == nullptr) {
226+
g_warning(
227+
"Event response for event id %ld received, but event was received "
228+
"out of order, or is unknown.",
229+
data->id);
230+
} else {
231+
handled = fl_value_get_bool(handled_value);
232+
if (!handled) {
233+
if (self->text_input_plugin != nullptr) {
234+
// Propagate the event to the text input plugin.
235+
handled = fl_text_input_plugin_filter_keypress(
236+
self->text_input_plugin, event);
237+
}
238+
// Dispatch the event to other GTK windows if the text input plugin
239+
// didn't handle it. We keep track of the event id so we can recognize
240+
// the event when our window receives it again and not respond to it. If
241+
// the response callback is set, then use that instead.
242+
if (!handled && self->response_callback == nullptr) {
243+
gdk_event_put(reinterpret_cast<GdkEvent*>(event));
244+
}
245+
}
246+
}
247+
}
248+
249+
if (handled) {
250+
// Because the event was handled, we no longer need to track it. Unhandled
251+
// events will be removed when the event is re-dispatched to the window.
252+
remove_pending_event(self, data->id);
253+
}
254+
255+
if (self->response_callback != nullptr) {
256+
self->response_callback(object, message, handled, data->user_data);
257+
}
258+
}
259+
260+
// Disposes of an FlKeyEventPlugin instance.
32261
static void fl_key_event_plugin_dispose(GObject* object) {
33262
FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN(object);
34263

35264
g_clear_object(&self->channel);
265+
g_object_remove_weak_pointer(
266+
G_OBJECT(self->text_input_plugin),
267+
reinterpret_cast<gpointer*>(&(self->text_input_plugin)));
268+
g_ptr_array_free(self->pending_events, TRUE);
36269

37270
G_OBJECT_CLASS(fl_key_event_plugin_parent_class)->dispose(object);
38271
}
39272

273+
// Initializes the FlKeyEventPlugin class methods.
40274
static void fl_key_event_plugin_class_init(FlKeyEventPluginClass* klass) {
41275
G_OBJECT_CLASS(klass)->dispose = fl_key_event_plugin_dispose;
42276
}
43277

278+
// Initializes an FlKeyEventPlugin instance.
44279
static void fl_key_event_plugin_init(FlKeyEventPlugin* self) {}
45280

46-
FlKeyEventPlugin* fl_key_event_plugin_new(FlBinaryMessenger* messenger,
47-
GAsyncReadyCallback response_callback,
48-
const char* channel_name) {
281+
// Creates a new FlKeyEventPlugin instance, with a messenger used to send
282+
// messages to the framework, an FlTextInputPlugin used to handle key events
283+
// that the framework doesn't handle. Mainly for testing purposes, it also takes
284+
// an optional callback to call when a response is received, and an optional
285+
// channel name to use when sending messages.
286+
FlKeyEventPlugin* fl_key_event_plugin_new(
287+
FlBinaryMessenger* messenger,
288+
FlTextInputPlugin* text_input_plugin,
289+
FlKeyEventPluginCallback response_callback,
290+
const char* channel_name) {
49291
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
292+
g_return_val_if_fail(FL_IS_TEXT_INPUT_PLUGIN(text_input_plugin), nullptr);
50293

51294
FlKeyEventPlugin* self = FL_KEY_EVENT_PLUGIN(
52295
g_object_new(fl_key_event_plugin_get_type(), nullptr));
@@ -56,15 +299,37 @@ FlKeyEventPlugin* fl_key_event_plugin_new(FlBinaryMessenger* messenger,
56299
messenger, channel_name == nullptr ? kChannelName : channel_name,
57300
FL_MESSAGE_CODEC(codec));
58301
self->response_callback = response_callback;
302+
// Add a weak pointer so we know if the text input plugin goes away.
303+
g_object_add_weak_pointer(
304+
G_OBJECT(text_input_plugin),
305+
reinterpret_cast<gpointer*>(&(self->text_input_plugin)));
306+
self->text_input_plugin = text_input_plugin;
59307

308+
self->pending_events = g_ptr_array_new_with_free_func(g_object_unref);
60309
return self;
61310
}
62311

63-
void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self,
312+
// Sends a key event to the framework.
313+
bool fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self,
64314
GdkEventKey* event,
65315
gpointer user_data) {
66-
g_return_if_fail(FL_IS_KEY_EVENT_PLUGIN(self));
67-
g_return_if_fail(event != nullptr);
316+
g_return_val_if_fail(FL_IS_KEY_EVENT_PLUGIN(self), FALSE);
317+
g_return_val_if_fail(event != nullptr, FALSE);
318+
319+
// Get an ID for the event, so we can match them up when we get a response
320+
// from the framework. Use the event time, type, and hardware keycode as a
321+
// unique ID, since they are part of the event structure that we can look up
322+
// when we receive a random event that may or may not have been
323+
// tracked/produced by this code.
324+
uint64_t id = get_event_id(event);
325+
if (self->pending_events->len != 0 &&
326+
FL_KEY_EVENT_PAIR(g_ptr_array_index(self->pending_events, 0))->id == id) {
327+
// If the event is at the head of the queue of pending events we've seen,
328+
// and has the same id, then we know that this is a re-dispatched event, and
329+
// we shouldn't respond to it, but we should remove it from tracking.
330+
remove_pending_event(self, id);
331+
return FALSE;
332+
}
68333

69334
const gchar* type;
70335
switch (event->type) {
@@ -75,7 +340,7 @@ void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self,
75340
type = kTypeValueUp;
76341
break;
77342
default:
78-
return;
343+
return FALSE;
79344
}
80345

81346
int64_t scan_code = event->hardware_keycode;
@@ -109,9 +374,9 @@ void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self,
109374
// Remove lock states from state mask.
110375
guint state = event->state & ~(GDK_LOCK_MASK | GDK_MOD2_MASK);
111376

112-
static bool shift_lock_pressed = false;
113-
static bool caps_lock_pressed = false;
114-
static bool num_lock_pressed = false;
377+
static bool shift_lock_pressed = FALSE;
378+
static bool caps_lock_pressed = FALSE;
379+
static bool num_lock_pressed = FALSE;
115380
switch (event->keyval) {
116381
case GDK_KEY_Num_Lock:
117382
num_lock_pressed = event->type == GDK_KEY_PRESS;
@@ -144,6 +409,14 @@ void fl_key_event_plugin_send_key_event(FlKeyEventPlugin* self,
144409
fl_value_new_int(unicodeScalarValues));
145410
}
146411

412+
// Track the event as pending a response from the framework.
413+
add_pending_event(self, id, event);
414+
FlKeyEventResponseData* data =
415+
fl_key_event_response_data_new(self, id, user_data);
416+
// Send the message off to the framework for handling (or not).
147417
fl_basic_message_channel_send(self->channel, message, nullptr,
148-
self->response_callback, user_data);
418+
handle_response, data);
419+
// Return true before we know what the framework will do, because if it
420+
// doesn't handle the key, we'll re-dispatch it later.
421+
return TRUE;
149422
}

0 commit comments

Comments
 (0)