// 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_plugin.h" #include #include #include "flutter/shell/platform/linux/public/flutter_linux/fl_json_method_codec.h" #include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h" static constexpr char kChannelName[] = "flutter/platform"; static constexpr char kBadArgumentsError[] = "Bad Arguments"; static constexpr char kUnknownClipboardFormatError[] = "Unknown Clipboard Format"; static constexpr char kFailedError[] = "Failed"; static constexpr char kGetClipboardDataMethod[] = "Clipboard.getData"; static constexpr char kSetClipboardDataMethod[] = "Clipboard.setData"; static constexpr char kClipboardHasStringsMethod[] = "Clipboard.hasStrings"; static constexpr char kSystemNavigatorPopMethod[] = "SystemNavigator.pop"; static constexpr char kTextKey[] = "text"; static constexpr char kValueKey[] = "value"; static constexpr char kTextPlainFormat[] = "text/plain"; struct _FlPlatformPlugin { GObject parent_instance; FlMethodChannel* channel; }; G_DEFINE_TYPE(FlPlatformPlugin, fl_platform_plugin, G_TYPE_OBJECT) // Sends the method call response to Flutter. static void send_response(FlMethodCall* method_call, FlMethodResponse* response) { g_autoptr(GError) error = nullptr; if (!fl_method_call_respond(method_call, response, &error)) { g_warning("Failed to send method call response: %s", error->message); } } // 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); g_autoptr(FlValue) result = nullptr; if (text != nullptr) { result = fl_value_new_map(); fl_value_set_string_take(result, kTextKey, fl_value_new_string(text)); } g_autoptr(FlMethodResponse) response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); send_response(method_call, response); } // 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); g_autoptr(FlValue) result = fl_value_new_map(); fl_value_set_string_take( result, kValueKey, fl_value_new_bool(text != nullptr && strlen(text) > 0)); g_autoptr(FlMethodResponse) response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); send_response(method_call, response); } // Called when Flutter wants to copy to the clipboard. static FlMethodResponse* clipboard_set_data(FlPlatformPlugin* self, FlValue* args) { if (fl_value_get_type(args) != FL_VALUE_TYPE_MAP) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, "Argument map missing or malformed", nullptr)); } FlValue* text_value = fl_value_lookup_string(args, kTextKey); if (text_value == nullptr || fl_value_get_type(text_value) != FL_VALUE_TYPE_STRING) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, "Missing clipboard text", nullptr)); } GtkClipboard* clipboard = gtk_clipboard_get_default(gdk_display_get_default()); gtk_clipboard_set_text(clipboard, fl_value_get_string(text_value), -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_async(FlPlatformPlugin* self, FlMethodCall* method_call) { FlValue* args = fl_method_call_get_args(method_call); if (fl_value_get_type(args) != FL_VALUE_TYPE_STRING) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kBadArgumentsError, "Expected string", nullptr)); } const gchar* format = fl_value_get_string(args); 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_async( FlPlatformPlugin* self, FlMethodCall* method_call) { 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; } // Called when Flutter wants to quit the application. static FlMethodResponse* system_navigator_pop(FlPlatformPlugin* self) { GApplication* app = g_application_get_default(); if (app == nullptr) { return FL_METHOD_RESPONSE(fl_method_error_response_new( kFailedError, "Unable to get GApplication", nullptr)); } g_application_quit(app); return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); } // Called when a method call is received from Flutter. static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) { FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(user_data); const gchar* method = fl_method_call_get_name(method_call); FlValue* args = fl_method_call_get_args(method_call); g_autoptr(FlMethodResponse) response = nullptr; if (strcmp(method, kSetClipboardDataMethod) == 0) { response = clipboard_set_data(self, args); } else if (strcmp(method, kGetClipboardDataMethod) == 0) { response = clipboard_get_data_async(self, method_call); } else if (strcmp(method, kClipboardHasStringsMethod) == 0) { response = clipboard_has_strings_async(self, method_call); } else if (strcmp(method, kSystemNavigatorPopMethod) == 0) { response = system_navigator_pop(self); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); } if (response != nullptr) { send_response(method_call, response); } } static void fl_platform_plugin_dispose(GObject* object) { FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(object); g_clear_object(&self->channel); G_OBJECT_CLASS(fl_platform_plugin_parent_class)->dispose(object); } static void fl_platform_plugin_class_init(FlPlatformPluginClass* klass) { G_OBJECT_CLASS(klass)->dispose = fl_platform_plugin_dispose; } static void fl_platform_plugin_init(FlPlatformPlugin* self) {} FlPlatformPlugin* fl_platform_plugin_new(FlBinaryMessenger* messenger) { g_return_val_if_fail(FL_IS_BINARY_MESSENGER(messenger), nullptr); FlPlatformPlugin* self = FL_PLATFORM_PLUGIN(g_object_new(fl_platform_plugin_get_type(), nullptr)); g_autoptr(FlJsonMethodCodec) codec = fl_json_method_codec_new(); self->channel = fl_method_channel_new(messenger, kChannelName, FL_METHOD_CODEC(codec)); fl_method_channel_set_method_call_handler(self->channel, method_call_cb, self, nullptr); return self; }