blob: 5be3470f0a1e15385b7b65eccf8a4a5d5906bba2 [file] [log] [blame]
// 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_platform_handler.h"
#include <gtk/gtk.h>
#include <cstring>
#include "flutter/shell/platform/linux/fl_platform_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
static constexpr char kInProgressError[] = "In Progress";
static constexpr char kUnknownClipboardFormatError[] =
"Unknown Clipboard Format";
static constexpr char kTextPlainFormat[] = "text/plain";
static constexpr char kSoundTypeAlert[] = "SystemSoundType.alert";
static constexpr char kSoundTypeClick[] = "SystemSoundType.click";
struct _FlPlatformHandler {
GObject parent_instance;
FlPlatformChannel* channel;
FlMethodCall* exit_application_method_call;
bool app_initialization_complete;
GCancellable* cancellable;
};
G_DEFINE_TYPE(FlPlatformHandler, fl_platform_handler, G_TYPE_OBJECT)
// Called when clipboard text received.
static void clipboard_text_cb(GtkClipboard* clipboard,
const gchar* text,
gpointer user_data) {
g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
fl_platform_channel_respond_clipboard_get_data(method_call, text);
}
// Called when clipboard text received during has_strings.
static void clipboard_text_has_strings_cb(GtkClipboard* clipboard,
const gchar* text,
gpointer user_data) {
g_autoptr(FlMethodCall) method_call = FL_METHOD_CALL(user_data);
fl_platform_channel_respond_clipboard_has_strings(
method_call, text != nullptr && strlen(text) > 0);
}
// Called when Flutter wants to copy to the clipboard.
static FlMethodResponse* clipboard_set_data(FlMethodCall* method_call,
const gchar* text,
gpointer user_data) {
GtkClipboard* clipboard =
gtk_clipboard_get_default(gdk_display_get_default());
gtk_clipboard_set_text(clipboard, text, -1);
return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr));
}
// Called when Flutter wants to paste from the clipboard.
static FlMethodResponse* clipboard_get_data(FlMethodCall* method_call,
const gchar* format,
gpointer user_data) {
if (strcmp(format, kTextPlainFormat) != 0) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kUnknownClipboardFormatError, "GTK clipboard API only supports text",
nullptr));
}
GtkClipboard* clipboard =
gtk_clipboard_get_default(gdk_display_get_default());
gtk_clipboard_request_text(clipboard, clipboard_text_cb,
g_object_ref(method_call));
// Will respond later.
return nullptr;
}
// Called when Flutter wants to know if the content of the clipboard is able to
// be pasted, without actually accessing the clipboard content itself.
static FlMethodResponse* clipboard_has_strings(FlMethodCall* method_call,
gpointer user_data) {
GtkClipboard* clipboard =
gtk_clipboard_get_default(gdk_display_get_default());
gtk_clipboard_request_text(clipboard, clipboard_text_has_strings_cb,
g_object_ref(method_call));
// Will respond later.
return nullptr;
}
// Quit this application
static void quit_application() {
GApplication* app = g_application_get_default();
if (app == nullptr) {
// Unable to gracefully quit, so just exit the process.
exit(0);
}
// GtkApplication windows contain a reference back to the application.
// Break them so the application object can cleanup.
// See https://gitlab.gnome.org/GNOME/gtk/-/issues/6190
if (GTK_IS_APPLICATION(app)) {
// List is copied as it will be modified as windows are disconnected from
// the application.
g_autoptr(GList) windows =
g_list_copy(gtk_application_get_windows(GTK_APPLICATION(app)));
for (GList* link = windows; link != NULL; link = link->next) {
GtkWidget* window = GTK_WIDGET(link->data);
gtk_window_set_application(GTK_WINDOW(window), NULL);
}
}
g_application_quit(app);
}
// Handle response of System.requestAppExit.
static void request_app_exit_response_cb(GObject* object,
GAsyncResult* result,
gpointer user_data) {
FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data);
g_autoptr(GError) error = nullptr;
FlPlatformChannelExitResponse exit_response;
if (!fl_platform_channel_system_request_app_exit_finish(
object, result, &exit_response, &error)) {
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
return;
}
g_warning("Failed to complete System.requestAppExit: %s", error->message);
quit_application();
return;
}
if (exit_response == FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT) {
quit_application();
}
// If request was due to a request from Flutter, pass result back.
if (self->exit_application_method_call != nullptr) {
fl_platform_channel_respond_system_exit_application(
self->exit_application_method_call, exit_response);
}
}
// Send a request to Flutter to exit the application, but only if it's ready for
// a request.
static void request_app_exit(FlPlatformHandler* self,
FlPlatformChannelExitType type) {
if (!self->app_initialization_complete ||
type == FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED) {
quit_application();
return;
}
fl_platform_channel_system_request_app_exit(
self->channel, type, self->cancellable, request_app_exit_response_cb,
self);
}
// Called when the Dart app has finished initialization and is ready to handle
// requests. For the Flutter framework, this means after the ServicesBinding has
// been initialized and it sends a System.initializationComplete message.
static void system_initialization_complete(gpointer user_data) {
FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data);
self->app_initialization_complete = TRUE;
}
// Called when Flutter wants to exit the application.
static FlMethodResponse* system_exit_application(FlMethodCall* method_call,
FlPlatformChannelExitType type,
gpointer user_data) {
FlPlatformHandler* self = FL_PLATFORM_HANDLER(user_data);
// Save method call to respond to when our request to Flutter completes.
if (self->exit_application_method_call != nullptr) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kInProgressError, "Request already in progress", nullptr));
}
self->exit_application_method_call =
FL_METHOD_CALL(g_object_ref(method_call));
// Requested to immediately quit if the app hasn't yet signaled that it is
// ready to handle requests, or if the type of exit requested is "required".
if (!self->app_initialization_complete ||
type == FL_PLATFORM_CHANNEL_EXIT_TYPE_REQUIRED) {
quit_application();
return fl_platform_channel_make_system_request_app_exit_response(
FL_PLATFORM_CHANNEL_EXIT_RESPONSE_EXIT);
}
// Send the request back to Flutter to follow the standard process.
request_app_exit(self, type);
// Will respond later.
return nullptr;
}
// Called when Flutter wants to play a sound.
static void system_sound_play(const gchar* type, gpointer user_data) {
if (strcmp(type, kSoundTypeAlert) == 0) {
GdkDisplay* display = gdk_display_get_default();
if (display != nullptr) {
gdk_display_beep(display);
}
} else if (strcmp(type, kSoundTypeClick) == 0) {
// We don't make sounds for keyboard on desktops.
} else {
g_warning("Ignoring unknown sound type %s in SystemSound.play.\n", type);
}
}
// Called when Flutter wants to quit the application.
static void system_navigator_pop(gpointer user_data) {
quit_application();
}
static void fl_platform_handler_dispose(GObject* object) {
FlPlatformHandler* self = FL_PLATFORM_HANDLER(object);
g_cancellable_cancel(self->cancellable);
g_clear_object(&self->channel);
g_clear_object(&self->exit_application_method_call);
g_clear_object(&self->cancellable);
G_OBJECT_CLASS(fl_platform_handler_parent_class)->dispose(object);
}
static void fl_platform_handler_class_init(FlPlatformHandlerClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_platform_handler_dispose;
}
static void fl_platform_handler_init(FlPlatformHandler* self) {
self->cancellable = g_cancellable_new();
}
static FlPlatformChannelVTable platform_channel_vtable = {
.clipboard_set_data = clipboard_set_data,
.clipboard_get_data = clipboard_get_data,
.clipboard_has_strings = clipboard_has_strings,
.system_exit_application = system_exit_application,
.system_initialization_complete = system_initialization_complete,
.system_sound_play = system_sound_play,
.system_navigator_pop = system_navigator_pop,
};
FlPlatformHandler* fl_platform_handler_new(FlBinaryMessenger* messenger) {
g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr);
FlPlatformHandler* self = FL_PLATFORM_HANDLER(
g_object_new(fl_platform_handler_get_type(), nullptr));
self->channel =
fl_platform_channel_new(messenger, &platform_channel_vtable, self);
self->app_initialization_complete = FALSE;
return self;
}
void fl_platform_handler_request_app_exit(FlPlatformHandler* self) {
g_return_if_fail(FL_IS_PLATFORM_HANDLER(self));
// Request a cancellable exit.
request_app_exit(self, FL_PLATFORM_CHANNEL_EXIT_TYPE_CANCELABLE);
}