forked from flutter/engine
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathfl_key_channel_responder.cc
292 lines (245 loc) · 11 KB
/
fl_key_channel_responder.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "flutter/shell/platform/linux/fl_key_channel_responder.h"
#include <gtk/gtk.h>
#include <cinttypes>
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_message_codec.h"
static constexpr char kChannelName[] = "flutter/keyevent";
static constexpr char kTypeKey[] = "type";
static constexpr char kTypeValueUp[] = "keyup";
static constexpr char kTypeValueDown[] = "keydown";
static constexpr char kKeymapKey[] = "keymap";
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";
static constexpr char kLinuxKeymap[] = "linux";
/* Declare and define FlKeyChannelUserData */
/**
* FlKeyChannelUserData:
* The user_data used when #FlKeyChannelResponder sends message through the
* channel.
*/
G_DECLARE_FINAL_TYPE(FlKeyChannelUserData,
fl_key_channel_user_data,
FL,
KEY_CHANNEL_USER_DATA,
GObject);
struct _FlKeyChannelUserData {
GObject parent_instance;
// The current responder.
FlKeyChannelResponder* responder;
// The callback provided by the caller #FlKeyboardHandler.
FlKeyResponderAsyncCallback callback;
// The user_data provided by the caller #FlKeyboardHandler.
gpointer user_data;
};
// Definition for FlKeyChannelUserData private class.
G_DEFINE_TYPE(FlKeyChannelUserData, fl_key_channel_user_data, G_TYPE_OBJECT)
// Dispose method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_dispose(GObject* object) {
g_return_if_fail(FL_IS_KEY_CHANNEL_USER_DATA(object));
FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(object);
if (self->responder != nullptr) {
g_object_remove_weak_pointer(
G_OBJECT(self->responder),
reinterpret_cast<gpointer*>(&(self->responder)));
self->responder = nullptr;
}
}
// Class initialization method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_class_init(
FlKeyChannelUserDataClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_channel_user_data_dispose;
}
// Instance initialization method for FlKeyChannelUserData private class.
static void fl_key_channel_user_data_init(FlKeyChannelUserData* self) {}
// Creates a new FlKeyChannelUserData private class with all information.
//
// The callback and the user_data might be nullptr.
static FlKeyChannelUserData* fl_key_channel_user_data_new(
FlKeyChannelResponder* responder,
FlKeyResponderAsyncCallback callback,
gpointer user_data) {
FlKeyChannelUserData* self = FL_KEY_CHANNEL_USER_DATA(
g_object_new(fl_key_channel_user_data_get_type(), nullptr));
self->responder = responder;
// Add a weak pointer so we can know if the key event responder disappeared
// while the framework was responding.
g_object_add_weak_pointer(G_OBJECT(responder),
reinterpret_cast<gpointer*>(&(self->responder)));
self->callback = callback;
self->user_data = user_data;
return self;
}
/* Define FlKeyChannelResponder */
// Definition of the FlKeyChannelResponder GObject class.
struct _FlKeyChannelResponder {
GObject parent_instance;
FlBasicMessageChannel* channel;
FlKeyChannelResponderMock* mock;
};
static void fl_key_channel_responder_iface_init(FlKeyResponderInterface* iface);
G_DEFINE_TYPE_WITH_CODE(
FlKeyChannelResponder,
fl_key_channel_responder,
G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE(FL_TYPE_KEY_RESPONDER,
fl_key_channel_responder_iface_init))
static void fl_key_channel_responder_handle_event(
FlKeyResponder* responder,
FlKeyEvent* event,
uint64_t specified_logical_key,
FlKeyResponderAsyncCallback callback,
gpointer user_data);
static void fl_key_channel_responder_iface_init(
FlKeyResponderInterface* iface) {
iface->handle_event = fl_key_channel_responder_handle_event;
}
/* Implement FlKeyChannelResponder */
// Handles a response from the method channel to a key event sent to the
// framework earlier.
static void handle_response(GObject* object,
GAsyncResult* result,
gpointer user_data) {
g_autoptr(FlKeyChannelUserData) data = FL_KEY_CHANNEL_USER_DATA(user_data);
// This is true if the weak pointer has been destroyed.
if (data->responder == nullptr) {
return;
}
FlKeyChannelResponder* self = data->responder;
g_autoptr(GError) error = nullptr;
FlBasicMessageChannel* messageChannel = FL_BASIC_MESSAGE_CHANNEL(object);
FlValue* message =
fl_basic_message_channel_send_finish(messageChannel, result, &error);
if (self->mock != nullptr && self->mock->value_converter != nullptr) {
message = self->mock->value_converter(message);
}
bool handled = false;
if (error != nullptr) {
g_warning("Unable to retrieve framework response: %s", error->message);
} else {
g_autoptr(FlValue) handled_value =
fl_value_lookup_string(message, "handled");
handled = fl_value_get_bool(handled_value);
}
data->callback(handled, data->user_data);
}
// Disposes of an FlKeyChannelResponder instance.
static void fl_key_channel_responder_dispose(GObject* object) {
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(object);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_key_channel_responder_parent_class)->dispose(object);
}
// Initializes the FlKeyChannelResponder class methods.
static void fl_key_channel_responder_class_init(
FlKeyChannelResponderClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_key_channel_responder_dispose;
}
// Initializes an FlKeyChannelResponder instance.
static void fl_key_channel_responder_init(FlKeyChannelResponder* self) {}
// Creates a new FlKeyChannelResponder instance, with a messenger used to send
// messages to the framework, and an FlTextInputHandler that is used to handle
// key events that the framework doesn't handle. Mainly for testing purposes, it
// also takes an optional callback to call when a response is received, and an
// optional channel name to use when sending messages.
FlKeyChannelResponder* fl_key_channel_responder_new(
FlBinaryMessenger* messenger,
FlKeyChannelResponderMock* mock) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlKeyChannelResponder* self = FL_KEY_CHANNEL_RESPONDER(
g_object_new(fl_key_channel_responder_get_type(), nullptr));
self->mock = mock;
g_autoptr(FlJsonMessageCodec) codec = fl_json_message_codec_new();
const char* channel_name =
mock == nullptr ? kChannelName : mock->channel_name;
self->channel = fl_basic_message_channel_new(messenger, channel_name,
FL_MESSAGE_CODEC(codec));
return self;
}
// Sends a key event to the framework.
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);
g_return_if_fail(event != nullptr);
g_return_if_fail(callback != nullptr);
const gchar* type =
fl_key_event_get_is_press(event) ? kTypeValueDown : kTypeValueUp;
int64_t scan_code = fl_key_event_get_keycode(event);
int64_t unicode_scarlar_values =
gdk_keyval_to_unicode(fl_key_event_get_keyval(event));
// For most modifier keys, GTK keeps track of the "pressed" state of the
// modifier keys. Flutter uses this information to keep modifier keys from
// being "stuck" when a key-up event is lost because it happens after the app
// loses focus.
//
// For Lock keys (ShiftLock, CapsLock, NumLock), however, GTK keeps track of
// the state of the locks themselves, not the "pressed" state of the key.
//
// Since Flutter expects the "pressed" state of the modifier keys, the lock
// state for these keys is discarded here, and it is substituted for the
// pressed state of the key.
//
// This code has the flaw that if a key event is missed due to the app losing
// focus, then this state will still think the key is pressed when it isn't,
// but that is no worse than for "regular" keys until we implement the
// sync/cancel events on app focus changes.
//
// This is necessary to do here instead of in the framework because Flutter
// does modifier key syncing in the framework, and will turn on/off these keys
// as being "pressed" whenever the lock is on, which breaks a lot of
// interactions (for example, if shift-lock is on, tab traversal is broken).
// Remove lock states from state mask.
guint state =
fl_key_event_get_state(event) & ~(GDK_LOCK_MASK | GDK_MOD2_MASK);
static bool shift_lock_pressed = FALSE;
static bool caps_lock_pressed = FALSE;
static bool num_lock_pressed = FALSE;
switch (fl_key_event_get_keyval(event)) {
case GDK_KEY_Num_Lock:
num_lock_pressed = fl_key_event_get_is_press(event);
break;
case GDK_KEY_Caps_Lock:
caps_lock_pressed = fl_key_event_get_is_press(event);
break;
case GDK_KEY_Shift_Lock:
shift_lock_pressed = fl_key_event_get_is_press(event);
break;
}
// Add back in the state matching the actual pressed state of the lock keys,
// not the lock states.
state |= (shift_lock_pressed || caps_lock_pressed) ? GDK_LOCK_MASK : 0x0;
state |= num_lock_pressed ? GDK_MOD2_MASK : 0x0;
g_autoptr(FlValue) message = fl_value_new_map();
fl_value_set_string_take(message, kTypeKey, fl_value_new_string(type));
fl_value_set_string_take(message, kKeymapKey,
fl_value_new_string(kLinuxKeymap));
fl_value_set_string_take(message, kScanCodeKey, fl_value_new_int(scan_code));
fl_value_set_string_take(message, kToolkitKey,
fl_value_new_string(kGtkToolkit));
fl_value_set_string_take(message, kKeyCodeKey,
fl_value_new_int(fl_key_event_get_keyval(event)));
fl_value_set_string_take(message, kModifiersKey, fl_value_new_int(state));
if (unicode_scarlar_values != 0) {
fl_value_set_string_take(message, kUnicodeScalarValuesKey,
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).
fl_basic_message_channel_send(self->channel, message, nullptr,
handle_response, data);
}