blob: 5a8cc2132595bc0a0fbcb3c31b0e71d7bb0cbcc7 [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 "include/file_selector_linux/file_selector_plugin.h"
#include <flutter_linux/flutter_linux.h>
#include <gtk/gtk.h>
#include "file_selector_plugin_private.h"
// From file_selector_linux.dart
const char kChannelName[] = "plugins.flutter.dev/file_selector_linux";
const char kOpenFileMethod[] = "openFile";
const char kGetSavePathMethod[] = "getSavePath";
const char kGetDirectoryPathMethod[] = "getDirectoryPath";
const char kAcceptedTypeGroupsKey[] = "acceptedTypeGroups";
const char kConfirmButtonTextKey[] = "confirmButtonText";
const char kInitialDirectoryKey[] = "initialDirectory";
const char kMultipleKey[] = "multiple";
const char kSuggestedNameKey[] = "suggestedName";
const char kTypeGroupLabelKey[] = "label";
const char kTypeGroupExtensionsKey[] = "extensions";
const char kTypeGroupMimeTypesKey[] = "mimeTypes";
// Errors
const char kBadArgumentsError[] = "Bad Arguments";
const char kNoScreenError[] = "No Screen";
struct _FlFileSelectorPlugin {
GObject parent_instance;
FlPluginRegistrar* registrar;
// Connection to Flutter engine.
FlMethodChannel* channel;
};
G_DEFINE_TYPE(FlFileSelectorPlugin, fl_file_selector_plugin, G_TYPE_OBJECT)
// Converts a type group received from Flutter into a GTK file filter.
static GtkFileFilter* type_group_to_filter(FlValue* value) {
g_autoptr(GtkFileFilter) filter = gtk_file_filter_new();
FlValue* label = fl_value_lookup_string(value, kTypeGroupLabelKey);
if (label != nullptr && fl_value_get_type(label) == FL_VALUE_TYPE_STRING) {
gtk_file_filter_set_name(filter, fl_value_get_string(label));
}
FlValue* extensions = fl_value_lookup_string(value, kTypeGroupExtensionsKey);
if (extensions != nullptr &&
fl_value_get_type(extensions) == FL_VALUE_TYPE_LIST) {
for (size_t i = 0; i < fl_value_get_length(extensions); i++) {
FlValue* v = fl_value_get_list_value(extensions, i);
const gchar* pattern = fl_value_get_string(v);
gtk_file_filter_add_pattern(filter, pattern);
}
}
FlValue* mime_types = fl_value_lookup_string(value, kTypeGroupMimeTypesKey);
if (mime_types != nullptr &&
fl_value_get_type(mime_types) == FL_VALUE_TYPE_LIST) {
for (size_t i = 0; i < fl_value_get_length(mime_types); i++) {
FlValue* v = fl_value_get_list_value(mime_types, i);
const gchar* pattern = fl_value_get_string(v);
gtk_file_filter_add_mime_type(filter, pattern);
}
}
return GTK_FILE_FILTER(g_object_ref(filter));
}
// Creates a GtkFileChooserNative for the given method call details.
static GtkFileChooserNative* create_dialog(
GtkWindow* window, GtkFileChooserAction action, const gchar* title,
const gchar* default_confirm_button_text, FlValue* properties) {
const gchar* confirm_button_text = default_confirm_button_text;
FlValue* value = fl_value_lookup_string(properties, kConfirmButtonTextKey);
if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_STRING)
confirm_button_text = fl_value_get_string(value);
g_autoptr(GtkFileChooserNative) dialog =
GTK_FILE_CHOOSER_NATIVE(gtk_file_chooser_native_new(
title, window, action, confirm_button_text, "_Cancel"));
value = fl_value_lookup_string(properties, kMultipleKey);
if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_BOOL) {
gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog),
fl_value_get_bool(value));
}
value = fl_value_lookup_string(properties, kInitialDirectoryKey);
if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_STRING) {
gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog),
fl_value_get_string(value));
}
value = fl_value_lookup_string(properties, kSuggestedNameKey);
if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_STRING) {
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
fl_value_get_string(value));
}
value = fl_value_lookup_string(properties, kAcceptedTypeGroupsKey);
if (value != nullptr && fl_value_get_type(value) == FL_VALUE_TYPE_LIST) {
for (size_t i = 0; i < fl_value_get_length(value); i++) {
FlValue* type_group = fl_value_get_list_value(value, i);
GtkFileFilter* filter = type_group_to_filter(type_group);
if (filter == nullptr) {
return nullptr;
}
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
}
}
return GTK_FILE_CHOOSER_NATIVE(g_object_ref(dialog));
}
// TODO(stuartmorgan): Move this logic back into method_call_cb once
// https://github.com/flutter/flutter/issues/88724 is fixed, and test
// through the public API instead. This only exists to move as much
// logic as possible behind the private entry point used by unit tests.
GtkFileChooserNative* create_dialog_for_method(GtkWindow* window,
const gchar* method,
FlValue* properties) {
if (strcmp(method, kOpenFileMethod) == 0) {
return create_dialog(window, GTK_FILE_CHOOSER_ACTION_OPEN, "Open File",
"_Open", properties);
} else if (strcmp(method, kGetDirectoryPathMethod) == 0) {
return create_dialog(window, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
"Choose Directory", "_Open", properties);
} else if (strcmp(method, kGetSavePathMethod) == 0) {
return create_dialog(window, GTK_FILE_CHOOSER_ACTION_SAVE, "Save File",
"_Save", properties);
}
return nullptr;
}
// Shows the requested dialog type.
static FlMethodResponse* show_dialog(FlFileSelectorPlugin* self,
const gchar* method, FlValue* properties,
bool return_list) {
if (fl_value_get_type(properties) != FL_VALUE_TYPE_MAP) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Argument map missing or malformed", nullptr));
}
FlView* view = fl_plugin_registrar_get_view(self->registrar);
if (view == nullptr) {
return FL_METHOD_RESPONSE(
fl_method_error_response_new(kNoScreenError, nullptr, nullptr));
}
GtkWindow* window = GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view)));
g_autoptr(GtkFileChooserNative) dialog =
create_dialog_for_method(window, method, properties);
if (dialog == nullptr) {
return FL_METHOD_RESPONSE(fl_method_error_response_new(
kBadArgumentsError, "Unable to create dialog from arguments", nullptr));
}
gint response = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog));
g_autoptr(FlValue) result = nullptr;
if (response == GTK_RESPONSE_ACCEPT) {
if (return_list) {
result = fl_value_new_list();
g_autoptr(GSList) filenames =
gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
for (GSList* link = filenames; link != nullptr; link = link->next) {
g_autofree gchar* filename = static_cast<gchar*>(link->data);
fl_value_append_take(result, fl_value_new_string(filename));
}
} else {
g_autofree gchar* filename =
gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
result = fl_value_new_string(filename);
}
}
return FL_METHOD_RESPONSE(fl_method_success_response_new(result));
}
// Called when a method call is received from Flutter.
static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call,
gpointer user_data) {
FlFileSelectorPlugin* self = FL_FILE_SELECTOR_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, kOpenFileMethod) == 0 ||
strcmp(method, kGetDirectoryPathMethod) == 0) {
response = show_dialog(self, method, args, true);
} else if (strcmp(method, kGetSavePathMethod) == 0) {
response = show_dialog(self, method, args, false);
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
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);
}
static void fl_file_selector_plugin_dispose(GObject* object) {
FlFileSelectorPlugin* self = FL_FILE_SELECTOR_PLUGIN(object);
g_clear_object(&self->registrar);
g_clear_object(&self->channel);
G_OBJECT_CLASS(fl_file_selector_plugin_parent_class)->dispose(object);
}
static void fl_file_selector_plugin_class_init(
FlFileSelectorPluginClass* klass) {
G_OBJECT_CLASS(klass)->dispose = fl_file_selector_plugin_dispose;
}
static void fl_file_selector_plugin_init(FlFileSelectorPlugin* self) {}
FlFileSelectorPlugin* fl_file_selector_plugin_new(
FlPluginRegistrar* registrar) {
FlFileSelectorPlugin* self = FL_FILE_SELECTOR_PLUGIN(
g_object_new(fl_file_selector_plugin_get_type(), nullptr));
self->registrar = FL_PLUGIN_REGISTRAR(g_object_ref(registrar));
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
self->channel =
fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
kChannelName, FL_METHOD_CODEC(codec));
fl_method_channel_set_method_call_handler(self->channel, method_call_cb,
g_object_ref(self), g_object_unref);
return self;
}
void file_selector_plugin_register_with_registrar(
FlPluginRegistrar* registrar) {
FlFileSelectorPlugin* plugin = fl_file_selector_plugin_new(registrar);
g_object_unref(plugin);
}