 32d87ac907
			
		
	
	32d87ac907
	
	
	
		
			
			This was basically done copying the gtk4 version and keeping the changes that weren't gtk4 specific. Fixes #6224 (the chooser doesn't close but we don't crash or such)
		
			
				
	
	
		
			545 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			545 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* -*- Mode: C; c-file-style: "gnu"; tab-width: 8 -*- */
 | |
| /* GTK - The GIMP Toolkit
 | |
|  * gtkfilechoosernativeportal.c: Portal File selector dialog
 | |
|  * Copyright (C) 2015, Red Hat, Inc.
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "config.h"
 | |
| 
 | |
| #include "gtkfilechoosernativeprivate.h"
 | |
| #include "gtknativedialogprivate.h"
 | |
| 
 | |
| #include "gtkprivate.h"
 | |
| #include "gtkdialog.h"
 | |
| #include "gtkfilechooserprivate.h"
 | |
| #include "gtkinvisible.h"
 | |
| #include "gtksizerequest.h"
 | |
| #include "gtktypebuiltins.h"
 | |
| #include "gtksettings.h"
 | |
| #include "gtktogglebutton.h"
 | |
| #include "gtkheaderbar.h"
 | |
| #include "gtklabel.h"
 | |
| #include "gtkmain.h"
 | |
| #include "gtkfilefilterprivate.h"
 | |
| #include "gtkwindowprivate.h"
 | |
| 
 | |
| typedef struct {
 | |
|   GtkFileChooserNative *self;
 | |
| 
 | |
|   GtkWidget *grab_widget;
 | |
| 
 | |
|   GDBusConnection *connection;
 | |
|   char *portal_handle;
 | |
|   guint portal_response_signal_id;
 | |
|   gboolean modal;
 | |
| 
 | |
|   gboolean hidden;
 | |
| 
 | |
|   const char *method_name;
 | |
| 
 | |
|   GtkWindow *exported_window;
 | |
| } FilechooserPortalData;
 | |
| 
 | |
| 
 | |
| static void
 | |
| filechooser_portal_data_clear (FilechooserPortalData *data)
 | |
| {
 | |
|   if (data->portal_response_signal_id != 0)
 | |
|     {
 | |
|       g_dbus_connection_signal_unsubscribe (data->connection,
 | |
|                                             data->portal_response_signal_id);
 | |
|       data->portal_response_signal_id = 0;
 | |
|     }
 | |
| 
 | |
|   g_clear_object (&data->connection);
 | |
| 
 | |
|   if (data->grab_widget)
 | |
|     {
 | |
|       gtk_grab_remove (data->grab_widget);
 | |
|       gtk_widget_destroy (data->grab_widget);
 | |
|       data->grab_widget = NULL;
 | |
|     }
 | |
| 
 | |
|   g_clear_object (&data->self);
 | |
| 
 | |
|   if (data->exported_window)
 | |
|     {
 | |
|       gtk_window_unexport_handle (data->exported_window);
 | |
|       data->exported_window = NULL;
 | |
|     }
 | |
| 
 | |
|   g_clear_pointer (&data->portal_handle, g_free);
 | |
| }
 | |
| 
 | |
| static void
 | |
| filechooser_portal_data_free (FilechooserPortalData *data)
 | |
| {
 | |
|   if (data != NULL)
 | |
|     {
 | |
|       filechooser_portal_data_clear (data);
 | |
|       g_free (data);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static void
 | |
| response_cb (GDBusConnection  *connection,
 | |
|              const char       *sender_name,
 | |
|              const char       *object_path,
 | |
|              const char       *interface_name,
 | |
|              const char       *signal_name,
 | |
|              GVariant         *parameters,
 | |
|              gpointer          user_data)
 | |
| {
 | |
|   GtkFileChooserNative *self = user_data;
 | |
|   FilechooserPortalData *data = self->mode_data;
 | |
|   guint32 portal_response;
 | |
|   int gtk_response;
 | |
|   const char **uris;
 | |
|   int i;
 | |
|   GVariant *response_data;
 | |
|   GVariant *choices = NULL;
 | |
|   GVariant *current_filter = NULL;
 | |
| 
 | |
|   g_variant_get (parameters, "(u@a{sv})", &portal_response, &response_data);
 | |
|   g_variant_lookup (response_data, "uris", "^a&s", &uris);
 | |
| 
 | |
|   choices = g_variant_lookup_value (response_data, "choices", G_VARIANT_TYPE ("a(ss)"));
 | |
|   if (choices)
 | |
|     {
 | |
|       for (i = 0; i < g_variant_n_children (choices); i++)
 | |
|         {
 | |
|           const char *id;
 | |
|           const char *selected;
 | |
|           g_variant_get_child (choices, i, "(&s&s)", &id, &selected);
 | |
|           gtk_file_chooser_set_choice (GTK_FILE_CHOOSER (self), id, selected);
 | |
|         }
 | |
|       g_variant_unref (choices);
 | |
|     }
 | |
| 
 | |
|   current_filter = g_variant_lookup_value (response_data, "current_filter", G_VARIANT_TYPE ("(sa(us))"));
 | |
|   if (current_filter)
 | |
|     {
 | |
|       GtkFileFilter *filter = gtk_file_filter_new_from_gvariant (current_filter);
 | |
|       const char *current_filter_name = gtk_file_filter_get_name (filter);
 | |
| 
 | |
|       /* Try to find  the given filter in the list of filters.
 | |
|        * Since filters are compared by pointer value, using the passed
 | |
|        * filter would otherwise not match in a comparison, even if
 | |
|        * a filter in the list of filters has been selected.
 | |
|        * We'll use the heuristic that if two filters have the same name,
 | |
|        * they must be the same.
 | |
|        * If there is no match, just set the filter as it was retrieved.
 | |
|        */
 | |
|       GtkFileFilter *filter_to_select = filter;
 | |
|       GSList *filters = gtk_file_chooser_list_filters (GTK_FILE_CHOOSER (self));
 | |
|       GSList *l;
 | |
| 
 | |
|       for (l = filters; l; l = l->next)
 | |
|         {
 | |
|           GtkFileFilter *f = l->data;
 | |
|           if (g_strcmp0 (gtk_file_filter_get_name (f), current_filter_name) == 0)
 | |
|             {
 | |
|               filter_to_select = f;
 | |
|               break;
 | |
|             }
 | |
|         }
 | |
|       g_slist_free (filters);
 | |
|       gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (self), filter_to_select);
 | |
|     }
 | |
| 
 | |
|   g_slist_free_full (self->custom_files, g_object_unref);
 | |
|   self->custom_files = NULL;
 | |
|   for (i = 0; uris[i]; i++)
 | |
|     self->custom_files = g_slist_prepend (self->custom_files, g_file_new_for_uri (uris[i]));
 | |
|   self->custom_files = g_slist_reverse (self->custom_files);
 | |
| 
 | |
|   g_free (uris);
 | |
|   g_variant_unref (response_data);
 | |
| 
 | |
|   switch (portal_response)
 | |
|     {
 | |
|     case 0:
 | |
|       gtk_response = GTK_RESPONSE_ACCEPT;
 | |
|       break;
 | |
|     case 1:
 | |
|       gtk_response = GTK_RESPONSE_CANCEL;
 | |
|       break;
 | |
|     case 2:
 | |
|     default:
 | |
|       gtk_response = GTK_RESPONSE_DELETE_EVENT;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|   /* Keep a reference on the native dialog until we can emit the response
 | |
|    * signal; filechooser_portal_data_free() will drop a reference on the
 | |
|    * dialog as well
 | |
|    */
 | |
|   g_object_ref (self);
 | |
| 
 | |
|   filechooser_portal_data_free (data);
 | |
|   self->mode_data = NULL;
 | |
| 
 | |
|   _gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), gtk_response);
 | |
| 
 | |
|   g_object_unref (self);
 | |
| }
 | |
| 
 | |
| static void
 | |
| send_close (FilechooserPortalData *data)
 | |
| {
 | |
|   GDBusMessage *message;
 | |
|   GError *error = NULL;
 | |
| 
 | |
|   message = g_dbus_message_new_method_call ("org.freedesktop.portal.Desktop",
 | |
|                                             data->portal_handle,
 | |
|                                             "org.freedesktop.portal.Request",
 | |
|                                             "Close");
 | |
| 
 | |
|   if (!g_dbus_connection_send_message (data->connection,
 | |
|                                        message,
 | |
|                                        G_DBUS_SEND_MESSAGE_FLAGS_NONE,
 | |
|                                        NULL, &error))
 | |
|     {
 | |
|       g_warning ("unable to send FileChooser Close message: %s", error->message);
 | |
|       g_error_free (error);
 | |
|     }
 | |
| 
 | |
|   g_object_unref (message);
 | |
| }
 | |
| 
 | |
| static void
 | |
| open_file_msg_cb (GObject *source_object,
 | |
|                   GAsyncResult *res,
 | |
|                   gpointer user_data)
 | |
| {
 | |
|   FilechooserPortalData *data = user_data;
 | |
|   GtkFileChooserNative *self = data->self;
 | |
|   GDBusMessage *reply;
 | |
|   GError *error = NULL;
 | |
|   char *handle = NULL;
 | |
| 
 | |
|   reply = g_dbus_connection_send_message_with_reply_finish (data->connection, res, &error);
 | |
| 
 | |
|   if (reply && g_dbus_message_to_gerror (reply, &error))
 | |
|     g_clear_object (&reply);
 | |
| 
 | |
|   if (reply == NULL)
 | |
|     {
 | |
|       if (!data->hidden)
 | |
|         {
 | |
|           filechooser_portal_data_free (data);
 | |
|           self->mode_data = NULL;
 | |
|         }
 | |
|       g_error_free (error);
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|   g_variant_get_child (g_dbus_message_get_body (reply), 0, "o", &handle);
 | |
| 
 | |
|   if (data->hidden)
 | |
|     {
 | |
|       /* The dialog was hidden before we got the handle, close it now */
 | |
|       filechooser_portal_data_free (data);
 | |
|       self->mode_data = NULL;
 | |
|     }
 | |
|   else if (strcmp (handle, data->portal_handle) != 0)
 | |
|     {
 | |
|       g_free (data->portal_handle);
 | |
|       data->portal_handle = g_steal_pointer (&handle);
 | |
|       g_dbus_connection_signal_unsubscribe (data->connection,
 | |
|                                             data->portal_response_signal_id);
 | |
| 
 | |
|       data->portal_response_signal_id =
 | |
|         g_dbus_connection_signal_subscribe (data->connection,
 | |
|                                             "org.freedesktop.portal.Desktop",
 | |
|                                             "org.freedesktop.portal.Request",
 | |
|                                             "Response",
 | |
|                                             data->portal_handle,
 | |
|                                             NULL,
 | |
|                                             G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
 | |
|                                             response_cb,
 | |
|                                             self, NULL);
 | |
|     }
 | |
| 
 | |
|   g_object_unref (reply);
 | |
|   g_free (handle);
 | |
| }
 | |
| 
 | |
| static GVariant *
 | |
| get_filters (GtkFileChooser *self)
 | |
| {
 | |
|   GSList *list, *l;
 | |
|   GVariantBuilder builder;
 | |
| 
 | |
|   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sa(us))"));
 | |
|   list = gtk_file_chooser_list_filters (self);
 | |
|   for (l = list; l; l = l->next)
 | |
|     {
 | |
|       GtkFileFilter *filter = l->data;
 | |
|       g_variant_builder_add (&builder, "@(sa(us))", gtk_file_filter_to_gvariant (filter));
 | |
|     }
 | |
|   g_slist_free (list);
 | |
| 
 | |
|   return g_variant_builder_end (&builder);
 | |
| }
 | |
| 
 | |
| static GVariant *
 | |
| gtk_file_chooser_native_choice_to_variant (GtkFileChooserNativeChoice *choice)
 | |
| {
 | |
|   GVariantBuilder choices;
 | |
|   int i;
 | |
| 
 | |
|   g_variant_builder_init (&choices, G_VARIANT_TYPE ("a(ss)"));
 | |
|   if (choice->options)
 | |
|     {
 | |
|       for (i = 0; choice->options[i]; i++)
 | |
|         g_variant_builder_add (&choices, "(&s&s)", choice->options[i], choice->option_labels[i]);
 | |
|     }
 | |
| 
 | |
|   return g_variant_new ("(&s&s@a(ss)&s)",
 | |
|                         choice->id,
 | |
|                         choice->label,
 | |
|                         g_variant_builder_end (&choices),
 | |
|                         choice->selected ? choice->selected : "");
 | |
| }
 | |
| 
 | |
| static GVariant *
 | |
| serialize_choices (GtkFileChooserNative *self)
 | |
| {
 | |
|   GVariantBuilder builder;
 | |
|   GSList *l;
 | |
| 
 | |
|   g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(ssa(ss)s)"));
 | |
|   for (l = self->choices; l; l = l->next)
 | |
|     {
 | |
|       GtkFileChooserNativeChoice *choice = l->data;
 | |
| 
 | |
|       g_variant_builder_add (&builder, "@(ssa(ss)s)",
 | |
|                              gtk_file_chooser_native_choice_to_variant (choice));
 | |
|     }
 | |
| 
 | |
|   return g_variant_builder_end (&builder);
 | |
| }
 | |
| 
 | |
| static void
 | |
| show_portal_file_chooser (GtkFileChooserNative *self,
 | |
|                           const char           *parent_window_str)
 | |
| {
 | |
|   FilechooserPortalData *data = self->mode_data;
 | |
|   GDBusMessage *message;
 | |
|   GVariantBuilder opt_builder;
 | |
|   gboolean multiple;
 | |
|   gboolean directory;
 | |
|   const char *title;
 | |
|   char *token;
 | |
| 
 | |
|   message = g_dbus_message_new_method_call ("org.freedesktop.portal.Desktop",
 | |
|                                             "/org/freedesktop/portal/desktop",
 | |
|                                             "org.freedesktop.portal.FileChooser",
 | |
|                                             data->method_name);
 | |
| 
 | |
|   data->portal_handle = gtk_get_portal_request_path (data->connection, &token);
 | |
|   data->portal_response_signal_id =
 | |
|         g_dbus_connection_signal_subscribe (data->connection,
 | |
|                                             "org.freedesktop.portal.Desktop",
 | |
|                                             "org.freedesktop.portal.Request",
 | |
|                                             "Response",
 | |
|                                             data->portal_handle,
 | |
|                                             NULL,
 | |
|                                             G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
 | |
|                                             response_cb,
 | |
|                                             self, NULL);
 | |
| 
 | |
|   multiple = gtk_file_chooser_get_select_multiple (GTK_FILE_CHOOSER (self));
 | |
|   directory = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self)) == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
 | |
|   g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
 | |
| 
 | |
|   g_variant_builder_add (&opt_builder, "{sv}", "handle_token",
 | |
|                          g_variant_new_string (token));
 | |
|   g_free (token);
 | |
| 
 | |
|   g_variant_builder_add (&opt_builder, "{sv}", "multiple",
 | |
|                          g_variant_new_boolean (multiple));
 | |
|   g_variant_builder_add (&opt_builder, "{sv}", "directory",
 | |
|                          g_variant_new_boolean (directory));
 | |
|   if (self->accept_label)
 | |
|     g_variant_builder_add (&opt_builder, "{sv}", "accept_label",
 | |
|                            g_variant_new_string (self->accept_label));
 | |
|   if (self->cancel_label)
 | |
|     g_variant_builder_add (&opt_builder, "{sv}", "cancel_label",
 | |
|                            g_variant_new_string (self->cancel_label));
 | |
|   g_variant_builder_add (&opt_builder, "{sv}", "modal",
 | |
|                          g_variant_new_boolean (data->modal));
 | |
|   g_variant_builder_add (&opt_builder, "{sv}", "filters", get_filters (GTK_FILE_CHOOSER (self)));
 | |
|   if (self->current_filter)
 | |
|     g_variant_builder_add (&opt_builder, "{sv}", "current_filter",
 | |
|                            gtk_file_filter_to_gvariant (self->current_filter));
 | |
|   if (self->current_name)
 | |
|     g_variant_builder_add (&opt_builder, "{sv}", "current_name",
 | |
|                            g_variant_new_string (GTK_FILE_CHOOSER_NATIVE (self)->current_name));
 | |
|   if (self->current_folder)
 | |
|     {
 | |
|       char *path;
 | |
| 
 | |
|       path = g_file_get_path (GTK_FILE_CHOOSER_NATIVE (self)->current_folder);
 | |
|       g_variant_builder_add (&opt_builder, "{sv}", "current_folder",
 | |
|                              g_variant_new_bytestring (path));
 | |
|       g_free (path);
 | |
|     }
 | |
|   if (self->current_file)
 | |
|     {
 | |
|       char *path;
 | |
| 
 | |
|       path = g_file_get_path (GTK_FILE_CHOOSER_NATIVE (self)->current_file);
 | |
|       g_variant_builder_add (&opt_builder, "{sv}", "current_file",
 | |
|                              g_variant_new_bytestring (path));
 | |
|       g_free (path);
 | |
|     }
 | |
| 
 | |
|   if (self->choices)
 | |
|     g_variant_builder_add (&opt_builder, "{sv}", "choices",
 | |
|                            serialize_choices (GTK_FILE_CHOOSER_NATIVE (self)));
 | |
| 
 | |
|   title = gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (self));
 | |
| 
 | |
|   g_dbus_message_set_body (message,
 | |
|                            g_variant_new ("(ss@a{sv})",
 | |
|                                           parent_window_str ? parent_window_str : "",
 | |
|                                           title ? title : "",
 | |
|                                           g_variant_builder_end (&opt_builder)));
 | |
| 
 | |
|   g_dbus_connection_send_message_with_reply (data->connection,
 | |
|                                              message,
 | |
|                                              G_DBUS_SEND_MESSAGE_FLAGS_NONE,
 | |
|                                              G_MAXINT,
 | |
|                                              NULL,
 | |
|                                              NULL,
 | |
|                                              open_file_msg_cb,
 | |
|                                              data);
 | |
| 
 | |
|   g_object_unref (message);
 | |
| }
 | |
| 
 | |
| static void
 | |
| window_handle_exported (GtkWindow  *window,
 | |
|                         const char *handle_str,
 | |
|                         gpointer    user_data)
 | |
| {
 | |
|   GtkFileChooserNative *self = user_data;
 | |
|   FilechooserPortalData *data = self->mode_data;
 | |
| 
 | |
|   if (data->modal)
 | |
|     {
 | |
|       GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (window));
 | |
| 
 | |
|       data->grab_widget = gtk_invisible_new_for_screen (screen);
 | |
|       gtk_grab_add (GTK_WIDGET (data->grab_widget));
 | |
|     }
 | |
| 
 | |
|   show_portal_file_chooser (self, handle_str);
 | |
| }
 | |
| 
 | |
| gboolean
 | |
| gtk_file_chooser_native_portal_show (GtkFileChooserNative *self)
 | |
| {
 | |
|   FilechooserPortalData *data;
 | |
|   GtkWindow *transient_for;
 | |
|   GDBusConnection *connection;
 | |
|   GtkFileChooserAction action;
 | |
|   const char *method_name;
 | |
| 
 | |
|   if (!gtk_should_use_portal ())
 | |
|     return FALSE;
 | |
| 
 | |
|   connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
 | |
|   if (connection == NULL)
 | |
|     return FALSE;
 | |
| 
 | |
|   action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (self));
 | |
| 
 | |
|   if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
 | |
|     method_name = "OpenFile";
 | |
|   else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
 | |
|     method_name = "SaveFile";
 | |
|   else if (action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
 | |
|     {
 | |
|       if (gtk_get_portal_interface_version (connection, "org.freedesktop.portal.FileChooser") < 3)
 | |
|         {
 | |
|           g_warning ("GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER is not supported by GtkFileChooserNativePortal because portal is too old");
 | |
|           return FALSE;
 | |
|         }
 | |
|       method_name = "OpenFile";
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       g_warning ("GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER is not supported by GtkFileChooserNativePortal");
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   data = g_new0 (FilechooserPortalData, 1);
 | |
|   data->self = g_object_ref (self);
 | |
|   data->connection = connection;
 | |
| 
 | |
|   data->method_name = method_name;
 | |
| 
 | |
|   if (gtk_native_dialog_get_modal (GTK_NATIVE_DIALOG (self)))
 | |
|     data->modal = TRUE;
 | |
| 
 | |
|   self->mode_data = data;
 | |
| 
 | |
|   transient_for = gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (self));
 | |
|   if (transient_for != NULL && gtk_widget_is_visible (GTK_WIDGET (transient_for)))
 | |
|     {
 | |
|       if (!gtk_window_export_handle (transient_for,
 | |
|                                      window_handle_exported,
 | |
|                                      self))
 | |
|         {
 | |
|           g_warning ("Failed to export handle, could not set transient for");
 | |
|           show_portal_file_chooser (self, NULL);
 | |
|         }
 | |
|       else
 | |
|         {
 | |
|           data->exported_window = transient_for;
 | |
|         }
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       show_portal_file_chooser (self, NULL);
 | |
|     }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| void
 | |
| gtk_file_chooser_native_portal_hide (GtkFileChooserNative *self)
 | |
| {
 | |
|   FilechooserPortalData *data = self->mode_data;
 | |
| 
 | |
|   /* This is always set while dialog visible */
 | |
|   g_assert (data != NULL);
 | |
| 
 | |
|   data->hidden = TRUE;
 | |
| 
 | |
|   if (data->portal_handle)
 | |
|     send_close (data);
 | |
| 
 | |
|   /* We clear the data because we might have in-flight async
 | |
|    * operations that can still access it
 | |
|    */
 | |
|   filechooser_portal_data_clear (data);
 | |
|   self->mode_data = NULL;
 | |
| }
 |