Flutter Engine
The Flutter Engine
fl_platform_plugin.cc
Go to the documentation of this file.
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_platform_plugin.h"
6
7#include <gtk/gtk.h>
8#include <cstring>
9
10#include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h"
11#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
12
13static constexpr char kChannelName[] = "flutter/platform";
14static constexpr char kBadArgumentsError[] = "Bad Arguments";
15static constexpr char kUnknownClipboardFormatError[] =
16 "Unknown Clipboard Format";
17static constexpr char kInProgressError[] = "In Progress";
18static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData";
19static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData";
20static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings";
21static constexpr char kExitApplicationMethod[] = "System.exitApplication";
22static constexpr char kRequestAppExitMethod[] = "System.requestAppExit";
23static constexpr char kInitializationCompleteMethod[] =
24 "System.initializationComplete";
25static constexpr char kPlaySoundMethod[] = "SystemSound.play";
26static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop";
27static constexpr char kTextKey[] = "text";
28static constexpr char kValueKey[] = "value";
29
30static constexpr char kExitTypeKey[] = "type";
31static constexpr char kExitTypeCancelable[] = "cancelable";
32static constexpr char kExitTypeRequired[] = "required";
33
34static constexpr char kExitResponseKey[] = "response";
35static constexpr char kExitResponseCancel[] = "cancel";
36static constexpr char kExitResponseExit[] = "exit";
37
38static constexpr char kTextPlainFormat[] = "text/plain";
39
40static constexpr char kSoundTypeAlert[] = "SystemSoundType.alert";
41static constexpr char kSoundTypeClick[] = "SystemSoundType.click";
42
45
46 FlMethodChannel* channel;
48 GCancellable* cancellable;
50};
51
52G_DEFINE_TYPE(FlPlatformPlugin, fl_platform_plugin, G_TYPE_OBJECT)
53
54// Sends the method call response to Flutter.
55static void send_response(FlMethodCall* method_call,
56 FlMethodResponse* response) {
57 g_autoptr(GError) error = nullptr;
58 if (!fl_method_call_respond(method_call, response, &error)) {
59 g_warning("Failed to send method call response: %s", error->message);
60 }
61}
62
63// Called when clipboard text received.
64static void clipboard_text_cb(GtkClipboard* clipboard,
65 const gchar* text,
66 gpointer user_data) {
67 g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
68
69 g_autoptr(FlValue) result = nullptr;
70 if (text != nullptr) {
73 }
74
75 g_autoptr(FlMethodResponse) response =
76 FL_METHOD_RESPONSE(fl_method_success_response_new(result));
77 send_response(method_call, response);
78}
79
80// Called when clipboard text received during has_strings.
81static void clipboard_text_has_strings_cb(GtkClipboard* clipboard,
82 const gchar* text,
83 gpointer user_data) {
84 g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
85
86 g_autoptr(FlValue) result = fl_value_new_map();
89 fl_value_new_bool(text != nullptr && strlen(text) > 0));
90
91 g_autoptr(FlMethodResponse) response =
92 FL_METHOD_RESPONSE(fl_method_success_response_new(result));
93 send_response(method_call, response);
94}
95
96// Called when Flutter wants to copy to the clipboard.
97static FlMethodResponse* clipboard_set_data(FlPlatformPlugin* self,
98 FlValue* args) {
100 return FL_METHOD_RESPONSE(fl_method_error_response_new(
101 kBadArgumentsError, "Argument map missing or malformed", nullptr));
102 }
103
105 if (text_value == nullptr ||
107 return FL_METHOD_RESPONSE(fl_method_error_response_new(
108 kBadArgumentsError, "Missing clipboard text", nullptr));
109 }
110
111 GtkClipboard* clipboard =
112 gtk_clipboard_get_default(gdk_display_get_default());
113 gtk_clipboard_set_text(clipboard, fl_value_get_string(text_value), -1);
114
115 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
116}
117
118// Called when Flutter wants to paste from the clipboard.
119static FlMethodResponse* clipboard_get_data_async(FlPlatformPlugin* self,
120 FlMethodCall* method_call) {
122
124 return FL_METHOD_RESPONSE(fl_method_error_response_new(
125 kBadArgumentsError, "Expected string", nullptr));
126 }
127
128 const gchar* format = fl_value_get_string(args);
129 if (strcmp(format, kTextPlainFormat) != 0) {
130 return FL_METHOD_RESPONSE(fl_method_error_response_new(
131 kUnknownClipboardFormatError, "GTK clipboard API only supports text",
132 nullptr));
133 }
134
135 GtkClipboard* clipboard =
136 gtk_clipboard_get_default(gdk_display_get_default());
137 gtk_clipboard_request_text(clipboard, clipboard_text_cb,
138 g_object_ref(method_call));
139
140 // Will respond later.
141 return nullptr;
142}
143
144// Called when Flutter wants to know if the content of the clipboard is able to
145// be pasted, without actually accessing the clipboard content itself.
146static FlMethodResponse* clipboard_has_strings_async(
147 FlPlatformPlugin* self,
148 FlMethodCall* method_call) {
149 GtkClipboard* clipboard =
150 gtk_clipboard_get_default(gdk_display_get_default());
151 gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb,
152 g_object_ref(method_call));
153
154 // Will respond later.
155 return nullptr;
156}
157
158// Get the exit response from a System.requestAppExit method call.
159static gchar* get_exit_response(FlMethodResponse* response) {
160 if (response == nullptr) {
161 return nullptr;
162 }
163
164 g_autoptr(GError) error = nullptr;
166 if (result == nullptr) {
167 g_warning("Error returned from System.requestAppExit: %s", error->message);
168 return nullptr;
169 }
171 g_warning("System.requestAppExit result argument map missing or malformed");
172 return nullptr;
173 }
174
176 if (fl_value_get_type(response_value) != FL_VALUE_TYPE_STRING) {
177 g_warning("Invalid response from System.requestAppExit");
178 return nullptr;
179 }
180 return g_strdup(fl_value_get_string(response_value));
181}
182
183// Quit this application
184static void quit_application() {
185 GApplication* app = g_application_get_default();
186 if (app == nullptr) {
187 // Unable to gracefully quit, so just exit the process.
188 exit(0);
189 }
190
191 // GtkApplication windows contain a reference back to the application.
192 // Break them so the application object can cleanup.
193 // See https://gitlab.gnome.org/GNOME/gtk/-/issues/6190
194 if (GTK_IS_APPLICATION(app)) {
195 GList* windows = gtk_application_get_windows(GTK_APPLICATION(app));
196 for (GList* link = windows; link != NULL; link = link->next) {
197 GtkWidget* window = GTK_WIDGET(link->data);
198 gtk_window_set_application(GTK_WINDOW(window), NULL);
199 }
200 }
201
202 g_application_quit(app);
203}
204
205// Handle response of System.requestAppExit.
206static void request_app_exit_response_cb(GObject* object,
207 GAsyncResult* result,
208 gpointer user_data) {
209 FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(user_data);
210
211 g_autoptr(GError) error = nullptr;
212 g_autoptr(FlMethodResponse) method_response =
213 fl_method_channel_invoke_method_finish(FL_METHOD_CHANNEL(object), result,
214 &error);
215 g_autofree gchar* exit_response = nullptr;
216 if (method_response == nullptr) {
217 if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
218 return;
219 }
220 g_warning("Failed to complete System.requestAppExit: %s", error->message);
221 } else {
222 exit_response = get_exit_response(method_response);
223 }
224 // If something went wrong, then just exit.
225 if (exit_response == nullptr) {
226 exit_response = g_strdup(kExitResponseExit);
227 }
228
229 if (g_str_equal(exit_response, kExitResponseExit)) {
231 } else if (g_str_equal(exit_response, kExitResponseCancel)) {
232 // Canceled - no action to take.
233 }
234
235 // If request was due to a request from Flutter, pass result back.
236 if (self->exit_application_method_call != nullptr) {
237 g_autoptr(FlValue) exit_result = fl_value_new_map();
239 fl_value_new_string(exit_response));
240 g_autoptr(FlMethodResponse) exit_response =
241 FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
242 if (!fl_method_call_respond(self->exit_application_method_call,
243 exit_response, &error)) {
244 g_warning("Failed to send response to System.exitApplication: %s",
245 error->message);
246 }
247 g_clear_object(&self->exit_application_method_call);
248 }
249}
250
251// Send a request to Flutter to exit the application, but only if it's ready for
252// a request.
253static void request_app_exit(FlPlatformPlugin* self, const char* type) {
254 g_autoptr(FlValue) args = fl_value_new_map();
255 if (!self->app_initialization_complete ||
256 g_str_equal(type, kExitTypeRequired)) {
258 return;
259 }
260
263 self->cancellable,
265}
266
267// Called when the Dart app has finished initialization and is ready to handle
268// requests. For the Flutter framework, this means after the ServicesBinding has
269// been initialized and it sends a System.initializationComplete message.
270static FlMethodResponse* system_intitialization_complete(
271 FlPlatformPlugin* self,
272 FlMethodCall* method_call) {
273 self->app_initialization_complete = TRUE;
274 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
275}
276
277// Called when Flutter wants to exit the application.
278static FlMethodResponse* system_exit_application(FlPlatformPlugin* self,
279 FlMethodCall* method_call) {
282 return FL_METHOD_RESPONSE(fl_method_error_response_new(
283 kBadArgumentsError, "Argument map missing or malformed", nullptr));
284 }
285
287 if (type_value == nullptr ||
289 return FL_METHOD_RESPONSE(fl_method_error_response_new(
290 kBadArgumentsError, "Missing type argument", nullptr));
291 }
292 const char* type = fl_value_get_string(type_value);
293
294 // Save method call to respond to when our request to Flutter completes.
295 if (self->exit_application_method_call != nullptr) {
296 return FL_METHOD_RESPONSE(fl_method_error_response_new(
297 kInProgressError, "Request already in progress", nullptr));
298 }
299 self->exit_application_method_call =
300 FL_METHOD_CALL(g_object_ref(method_call));
301
302 // Requested to immediately quit if the app hasn't yet signaled that it is
303 // ready to handle requests, or if the type of exit requested is "required".
304 if (!self->app_initialization_complete ||
305 g_str_equal(type, kExitTypeRequired)) {
307 g_autoptr(FlValue) exit_result = fl_value_new_map();
310 return FL_METHOD_RESPONSE(fl_method_success_response_new(exit_result));
311 }
312
313 // Send the request back to Flutter to follow the standard process.
315
316 // Will respond later.
317 return nullptr;
318}
319
320// Called when Flutter wants to play a sound.
321static FlMethodResponse* system_sound_play(FlPlatformPlugin* self,
322 FlValue* args) {
324 return FL_METHOD_RESPONSE(fl_method_error_response_new(
325 kBadArgumentsError, "Expected string", nullptr));
326 }
327
328 const gchar* type = fl_value_get_string(args);
329 if (strcmp(type, kSoundTypeAlert) == 0) {
330 GdkDisplay* display = gdk_display_get_default();
331 if (display != nullptr) {
332 gdk_display_beep(display);
333 }
334 } else if (strcmp(type, kSoundTypeClick) == 0) {
335 // We don't make sounds for keyboard on desktops.
336 } else {
337 g_warning("Ignoring unknown sound type %s in SystemSound.play.\n", type);
338 }
339
340 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
341}
342
343// Called when Flutter wants to quit the application.
344static FlMethodResponse* system_navigator_pop(FlPlatformPlugin* self) {
346 return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
347}
348
349// Called when a method call is received from Flutter.
350static void method_call_cb(FlMethodChannel* channel,
351 FlMethodCall* method_call,
352 gpointer user_data) {
353 FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(user_data);
354
355 const gchar* method = fl_method_call_get_name(method_call);
357
358 g_autoptr(FlMethodResponse) response = nullptr;
359 if (strcmp(method, kSetClipboardDataMethod) == 0) {
360 response = clipboard_set_data(self, args);
361 } else if (strcmp(method, kGetClipboardDataMethod) == 0) {
363 } else if (strcmp(method, kClipboardHasStringsMethod) == 0) {
365 } else if (strcmp(method, kExitApplicationMethod) == 0) {
367 } else if (strcmp(method, kInitializationCompleteMethod) == 0) {
369 } else if (strcmp(method, kPlaySoundMethod) == 0) {
370 response = system_sound_play(self, args);
371 } else if (strcmp(method, kSystemNavigatorPopMethod) == 0) {
372 response = system_navigator_pop(self);
373 } else {
374 response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
375 }
376
377 if (response != nullptr) {
378 send_response(method_call, response);
379 }
380}
381
382static void fl_platform_plugin_dispose(GObject* object) {
383 FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(object);
384
385 g_cancellable_cancel(self->cancellable);
386
387 g_clear_object(&self->channel);
388 g_clear_object(&self->exit_application_method_call);
389 g_clear_object(&self->cancellable);
390
391 G_OBJECT_CLASS(fl_platform_plugin_parent_class)->dispose(object);
392}
393
394static void fl_platform_plugin_class_init(FlPlatformPluginClass* klass) {
395 G_OBJECT_CLASS(klass)->dispose = fl_platform_plugin_dispose;
396}
397
398static void fl_platform_plugin_init(FlPlatformPlugin* self) {
399 self->cancellable = g_cancellable_new();
400}
401
402FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) {
403 g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
404
405 FlPlatformPlugin* self =
406 FL_PLATFORM_PLUGIN(g_object_new(fl_platform_plugin_get_type(), nullptr));
407
408 g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new();
409 self->channel =
410 fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec));
412 nullptr);
413 self->app_initialization_complete = FALSE;
414
415 return self;
416}
417
419 g_return_if_fail(FL_IS_PLATFORM_PLUGIN(self));
420 // Request a cancellable exit.
422}
GLenum type
GLFWwindow * window
Definition: main.cc:45
G_DEFINE_TYPE(FlBasicMessageChannelResponseHandle, fl_basic_message_channel_response_handle, G_TYPE_OBJECT) static void fl_basic_message_channel_response_handle_dispose(GObject *object)
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
G_MODULE_EXPORT FlJsonMethodCodec * fl_json_method_codec_new()
G_MODULE_EXPORT const gchar * fl_method_call_get_name(FlMethodCall *self)
G_MODULE_EXPORT gboolean fl_method_call_respond(FlMethodCall *self, FlMethodResponse *response, GError **error)
G_MODULE_EXPORT FlValue * fl_method_call_get_args(FlMethodCall *self)
G_MODULE_EXPORT FlMethodResponse * fl_method_channel_invoke_method_finish(FlMethodChannel *self, GAsyncResult *result, GError **error)
G_MODULE_EXPORT FlMethodChannel * fl_method_channel_new(FlBinaryMessenger *messenger, const gchar *name, FlMethodCodec *codec)
G_MODULE_EXPORT void fl_method_channel_invoke_method(FlMethodChannel *self, const gchar *method, FlValue *args, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
G_MODULE_EXPORT void fl_method_channel_set_method_call_handler(FlMethodChannel *self, FlMethodChannelMethodCallHandler handler, gpointer user_data, GDestroyNotify destroy_notify)
G_BEGIN_DECLS G_MODULE_EXPORT FlMethodCall * method_call
G_MODULE_EXPORT FlValue * fl_method_response_get_result(FlMethodResponse *self, GError **error)
G_MODULE_EXPORT FlMethodErrorResponse * fl_method_error_response_new(const gchar *code, const gchar *message, FlValue *details)
G_MODULE_EXPORT FlMethodSuccessResponse * fl_method_success_response_new(FlValue *result)
G_MODULE_EXPORT FlMethodNotImplementedResponse * fl_method_not_implemented_response_new()
const uint8_t uint32_t uint32_t GError ** error
static void method_call_cb(FlMethodChannel *channel, FlMethodCall *method_call, gpointer user_data)
static constexpr char kValueKey[]
static constexpr char kSystemNavigatorPopMethod[]
static void send_response(FlMethodCall *method_call, FlMethodResponse *response)
static constexpr char kInitializationCompleteMethod[]
static FlMethodResponse * clipboard_has_strings_async(FlPlatformPlugin *self, FlMethodCall *method_call)
static void clipboard_text_has_strings_cb(GtkClipboard *clipboard, const gchar *text, gpointer user_data)
static constexpr char kClipboardHasStringsMethod[]
static FlMethodResponse * system_exit_application(FlPlatformPlugin *self, FlMethodCall *method_call)
static void quit_application()
static constexpr char kExitTypeCancelable[]
static FlMethodResponse * clipboard_set_data(FlPlatformPlugin *self, FlValue *args)
FlPlatformPlugin * fl_platform_plugin_new(FlBinaryMessenger *messenger)
static constexpr char kGetClipboardDataMethod[]
static constexpr char kChannelName[]
void fl_platform_plugin_request_app_exit(FlPlatformPlugin *self)
static constexpr char kExitApplicationMethod[]
static void fl_platform_plugin_init(FlPlatformPlugin *self)
static constexpr char kExitResponseExit[]
static constexpr char kSoundTypeAlert[]
static FlMethodResponse * system_sound_play(FlPlatformPlugin *self, FlValue *args)
static constexpr char kRequestAppExitMethod[]
static constexpr char kUnknownClipboardFormatError[]
static FlMethodResponse * system_intitialization_complete(FlPlatformPlugin *self, FlMethodCall *method_call)
static constexpr char kBadArgumentsError[]
static void fl_platform_plugin_class_init(FlPlatformPluginClass *klass)
static constexpr char kExitTypeRequired[]
static constexpr char kExitResponseKey[]
static constexpr char kPlaySoundMethod[]
static void fl_platform_plugin_dispose(GObject *object)
static void request_app_exit(FlPlatformPlugin *self, const char *type)
static constexpr char kExitResponseCancel[]
static constexpr char kExitTypeKey[]
static constexpr char kSoundTypeClick[]
static gchar * get_exit_response(FlMethodResponse *response)
static FlMethodResponse * clipboard_get_data_async(FlPlatformPlugin *self, FlMethodCall *method_call)
static void clipboard_text_cb(GtkClipboard *clipboard, const gchar *text, gpointer user_data)
static constexpr char kTextPlainFormat[]
static void request_app_exit_response_cb(GObject *object, GAsyncResult *result, gpointer user_data)
static constexpr char kTextKey[]
static constexpr char kSetClipboardDataMethod[]
static FlMethodResponse * system_navigator_pop(FlPlatformPlugin *self)
static constexpr char kInProgressError[]
GAsyncResult * result
uint32_t uint32_t * format
G_MODULE_EXPORT FlValue * fl_value_new_map()
Definition: fl_value.cc:366
G_MODULE_EXPORT void fl_value_set_string_take(FlValue *self, const gchar *key, FlValue *value)
Definition: fl_value.cc:650
G_MODULE_EXPORT FlValue * fl_value_lookup_string(FlValue *self, const gchar *key)
Definition: fl_value.cc:811
G_MODULE_EXPORT FlValueType fl_value_get_type(FlValue *self)
Definition: fl_value.cc:466
G_MODULE_EXPORT FlValue * fl_value_new_string(const gchar *value)
Definition: fl_value.cc:276
G_MODULE_EXPORT FlValue * fl_value_new_bool(bool value)
Definition: fl_value.cc:255
G_MODULE_EXPORT const gchar * fl_value_get_string(FlValue *self)
Definition: fl_value.cc:682
typedefG_BEGIN_DECLS struct _FlValue FlValue
Definition: fl_value.h:42
@ FL_VALUE_TYPE_STRING
Definition: fl_value.h:69
@ FL_VALUE_TYPE_MAP
Definition: fl_value.h:75
std::u16string text
return FALSE
exit(kErrorExitCode)
def link(from_root, to_root)
Definition: dart_pkg.py:44
FlMethodChannel * channel
GCancellable * cancellable
FlMethodCall * exit_application_method_call
void * user_data