diff --git a/packages/file_selector/file_selector_linux/.gitignore b/packages/file_selector/file_selector_linux/.gitignore new file mode 100644 index 000000000000..f441e68ab541 --- /dev/null +++ b/packages/file_selector/file_selector_linux/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +.dart_tool/ + +.packages +.plugin_symlinks +.pub/ + +build/ diff --git a/packages/file_selector/file_selector_linux/.metadata b/packages/file_selector/file_selector_linux/.metadata new file mode 100644 index 000000000000..8949001803d3 --- /dev/null +++ b/packages/file_selector/file_selector_linux/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2783f8e2e14efec8b7e08f668dde61c40d128c24 + channel: dev + +project_type: plugin diff --git a/packages/file_selector/file_selector_linux/CHANGELOG.md b/packages/file_selector/file_selector_linux/CHANGELOG.md new file mode 100644 index 000000000000..6289e796d46a --- /dev/null +++ b/packages/file_selector/file_selector_linux/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.1.0 + +* Initial GtkFileChooserDialog based implementation for Linux. diff --git a/packages/file_selector/file_selector_linux/LICENSE b/packages/file_selector/file_selector_linux/LICENSE new file mode 100644 index 000000000000..67c7e2c52e46 --- /dev/null +++ b/packages/file_selector/file_selector_linux/LICENSE @@ -0,0 +1,25 @@ +Copyright 2020 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/file_selector/file_selector_linux/README.md b/packages/file_selector/file_selector_linux/README.md new file mode 100644 index 000000000000..cbdf200a1906 --- /dev/null +++ b/packages/file_selector/file_selector_linux/README.md @@ -0,0 +1,8 @@ +# file_selector_linux + +The linux implementation of [`file_selector`]. + +## Usage + +This package is already included as part of the `file_selector` package dependency, and will +be included when using `file_selector` as normal. You will need to use version 1.6.10 or newer. diff --git a/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart new file mode 100644 index 000000000000..0b148869ff43 --- /dev/null +++ b/packages/file_selector/file_selector_linux/lib/file_selector_linux.dart @@ -0,0 +1,3 @@ +// The file_selector_platform_interface defaults to MethodChannelFileSelector +// as its instance, which is all the Linux implementation needs. This file +// is here to silence warnings when publishing to pub. diff --git a/packages/file_selector/file_selector_linux/linux/CMakeLists.txt b/packages/file_selector/file_selector_linux/linux/CMakeLists.txt new file mode 100644 index 000000000000..a80449a8f21d --- /dev/null +++ b/packages/file_selector/file_selector_linux/linux/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.10) +set(PROJECT_NAME "file_selector_linux") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "file_selector_linux_plugin") + +add_library(${PLUGIN_NAME} SHARED + "file_selector_linux_plugin.cc" +) +apply_standard_settings(${PLUGIN_NAME}) +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin +set(file_selector_linux_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/packages/file_selector/file_selector_linux/linux/file_selector_linux_plugin.cc b/packages/file_selector/file_selector_linux/linux/file_selector_linux_plugin.cc new file mode 100644 index 000000000000..f0b67bde73e3 --- /dev/null +++ b/packages/file_selector/file_selector_linux/linux/file_selector_linux_plugin.cc @@ -0,0 +1,204 @@ +#include "include/file_selector_linux/file_selector_linux_plugin.h" + +#include +#include +#include +#include +#include + +#define FILE_SELECTOR_LINUX_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), file_selector_linux_plugin_get_type(), \ + FileSelectorLinuxPlugin)) + +struct _FileSelectorLinuxPlugin { + GObject parent_instance; + FlView* view; +}; + +G_DEFINE_TYPE(FileSelectorLinuxPlugin, file_selector_linux_plugin, + g_object_get_type()) + +static bool fl_value_is_valid(FlValue* value, FlValueType type) { + return value && fl_value_get_type(value) == type; +} + +static void fl_value_list_for_each(FlValue* list, GFunc func, + gpointer user_data) { + g_return_if_fail(func != nullptr); + if (!list || fl_value_get_type(list) != FL_VALUE_TYPE_LIST) { + return; + } + + size_t length = fl_value_get_length(list); + for (size_t j = 0; j < length; ++j) { + FlValue* value = fl_value_get_list_value(list, j); + func(value, user_data); + } +} + +static FlValue* file_chooser_get_filename(GtkFileChooser* chooser) { + gchar* filename = gtk_file_chooser_get_filename(chooser); + FlValue* value = fl_value_new_string(filename); + g_free(filename); + return value; +} + +static FlValue* file_chooser_get_filenames(GtkFileChooser* chooser) { + FlValue* value = fl_value_new_list(); + GSList* filenames = gtk_file_chooser_get_filenames(chooser); + while (filenames) { + const gchar* filename = static_cast(filenames->data); + fl_value_append_take(value, fl_value_new_string(filename)); + filenames = filenames->next; + } + g_slist_free(filenames); + return value; +} + +static void file_filter_add_pattern(gpointer data, gpointer user_data) { + FlValue* value = static_cast(data); + if (fl_value_is_valid(value, FL_VALUE_TYPE_STRING)) { + gchar* pattern = g_strdup_printf("*.%s", fl_value_get_string(value)); + gtk_file_filter_add_pattern(GTK_FILE_FILTER(user_data), pattern); + g_free(pattern); + } +} + +static void file_filter_add_mime_type(gpointer data, gpointer user_data) { + FlValue* value = static_cast(data); + if (fl_value_is_valid(value, FL_VALUE_TYPE_STRING)) { + gtk_file_filter_add_mime_type(GTK_FILE_FILTER(user_data), + fl_value_get_string(value)); + } +} + +static void file_chooser_add_filter(gpointer data, gpointer user_data) { + FlValue* type = static_cast(data); + if (fl_value_is_valid(type, FL_VALUE_TYPE_MAP)) { + GtkFileFilter* filter = gtk_file_filter_new(); + + FlValue* label = fl_value_lookup_string(type, "label"); + if (fl_value_is_valid(label, FL_VALUE_TYPE_STRING)) { + gtk_file_filter_set_name(filter, fl_value_get_string(label)); + } + + FlValue* extensions = fl_value_lookup_string(type, "extensions"); + fl_value_list_for_each(extensions, file_filter_add_pattern, filter); + + FlValue* mime_types = fl_value_lookup_string(type, "mimeTypes"); + fl_value_list_for_each(mime_types, file_filter_add_mime_type, filter); + + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(user_data), filter); + } +} + +static FlMethodResponse* file_chooser_show(GtkFileChooserAction action, + FlValue* args, FlView* view) { + FlMethodResponse* response = nullptr; + + const gchar* accept_label = _("_Open"); + FlValue* confirm = fl_value_lookup_string(args, "confirmButtonText"); + if (fl_value_is_valid(confirm, FL_VALUE_TYPE_STRING)) { + accept_label = fl_value_get_string(confirm); + } + + GtkWidget* parent_window = gtk_widget_get_toplevel(GTK_WIDGET(view)); + GtkWidget* dialog = gtk_file_chooser_dialog_new( + NULL, GTK_WINDOW(parent_window), action, _("_Cancel"), + GTK_RESPONSE_CANCEL, accept_label, GTK_RESPONSE_ACCEPT, NULL); + + GtkFileChooser* chooser = GTK_FILE_CHOOSER(dialog); + + FlValue* types = fl_value_lookup_string(args, "acceptedTypeGroups"); + fl_value_list_for_each(types, file_chooser_add_filter, chooser); + + FlValue* dir = fl_value_lookup_string(args, "initialDirectory"); + if (fl_value_is_valid(dir, FL_VALUE_TYPE_STRING)) { + gtk_file_chooser_set_current_folder(chooser, fl_value_get_string(dir)); + } + + FlValue* multiple = fl_value_lookup_string(args, "multiple"); + if (fl_value_is_valid(multiple, FL_VALUE_TYPE_BOOL)) { + gtk_file_chooser_set_select_multiple(chooser, fl_value_get_bool(multiple)); + } + + FlValue* name = fl_value_lookup_string(args, "suggestedName"); + if (fl_value_is_valid(name, FL_VALUE_TYPE_STRING)) { + gtk_file_chooser_set_current_name(chooser, fl_value_get_string(name)); + } + + gint res = gtk_dialog_run(GTK_DIALOG(dialog)); + if (res == GTK_RESPONSE_ACCEPT) { + if (action == GTK_FILE_CHOOSER_ACTION_OPEN) { + FlValue* filenames = file_chooser_get_filenames(chooser); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(filenames)); + } else { + FlValue* filename = file_chooser_get_filename(chooser); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(filename)); + } + } else { + response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } + + gtk_widget_destroy(dialog); + return response; +} + +static void file_selector_linux_plugin_handle_method_call( + FileSelectorLinuxPlugin* self, FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + static const GtkFileChooserAction none = GtkFileChooserAction(-1); + GtkFileChooserAction action = none; + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "openFile") == 0 || strcmp(method, "openFiles") == 0) { + action = GTK_FILE_CHOOSER_ACTION_OPEN; + } else if (strcmp(method, "getSavePath") == 0) { + action = GTK_FILE_CHOOSER_ACTION_SAVE; + } else if (strcmp(method, "getDirectoryPath") == 0) { + action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER; + } + + if (action != none) { + FlValue* args = fl_method_call_get_args(method_call); + response = file_chooser_show(action, args, self->view); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +static void file_selector_linux_plugin_dispose(GObject* object) { + G_OBJECT_CLASS(file_selector_linux_plugin_parent_class)->dispose(object); +} + +static void file_selector_linux_plugin_class_init( + FileSelectorLinuxPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = file_selector_linux_plugin_dispose; +} + +static void file_selector_linux_plugin_init(FileSelectorLinuxPlugin* self) {} + +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + FileSelectorLinuxPlugin* plugin = FILE_SELECTOR_LINUX_PLUGIN(user_data); + file_selector_linux_plugin_handle_method_call(plugin, method_call); +} + +void file_selector_linux_plugin_register_with_registrar( + FlPluginRegistrar* registrar) { + FileSelectorLinuxPlugin* plugin = FILE_SELECTOR_LINUX_PLUGIN( + g_object_new(file_selector_linux_plugin_get_type(), nullptr)); + plugin->view = fl_plugin_registrar_get_view(registrar); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = fl_method_channel_new( + fl_plugin_registrar_get_messenger(registrar), + "plugins.flutter.io/file_selector", FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler( + channel, method_call_cb, g_object_ref(plugin), g_object_unref); + + g_object_unref(plugin); +} diff --git a/packages/file_selector/file_selector_linux/linux/include/file_selector_linux/file_selector_linux_plugin.h b/packages/file_selector/file_selector_linux/linux/include/file_selector_linux/file_selector_linux_plugin.h new file mode 100644 index 000000000000..a0b3192db8c8 --- /dev/null +++ b/packages/file_selector/file_selector_linux/linux/include/file_selector_linux/file_selector_linux_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_FILE_SELECTOR_LINUX_PLUGIN_H_ +#define FLUTTER_PLUGIN_FILE_SELECTOR_LINUX_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _FileSelectorLinuxPlugin FileSelectorLinuxPlugin; +typedef struct { + GObjectClass parent_class; +} FileSelectorLinuxPluginClass; + +FLUTTER_PLUGIN_EXPORT GType file_selector_linux_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void file_selector_linux_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_FILE_SELECTOR_LINUX_PLUGIN_H_ diff --git a/packages/file_selector/file_selector_linux/pubspec.yaml b/packages/file_selector/file_selector_linux/pubspec.yaml new file mode 100644 index 000000000000..472ad7f71a75 --- /dev/null +++ b/packages/file_selector/file_selector_linux/pubspec.yaml @@ -0,0 +1,21 @@ +name: file_selector_linux +description: A Linux implementation of the file_selector plugin. +homepage: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector_linux +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 0.1.0 + +dependencies: + file_selector_platform_interface: ^1.0.0 + flutter: + sdk: flutter + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.12.8 <2.0.0" + +flutter: + plugin: + platforms: + linux: + pluginClass: FileSelectorLinuxPlugin