Compare commits

...

14 Commits

Author SHA1 Message Date
Alexander Larsson
e97a26070c FileChooserPortal: Update to work with lastest xdg-desktop-portal 2016-06-21 16:19:20 +02:00
Matthias Clasen
2631261597 Add gtk_show_uri_on_window
The gtk_show_uri API doesn't let us specify a parent window. With
portals, there may be an intermediate dialog, for which it is nice
to have parent window information, to place it properly.
2016-06-18 17:59:40 -04:00
Matthias Clasen
32457cded3 Use the portal when sandboxed
flatpak creates a file called /run/user/1000/flatpak-info that we
can use to determine whether we are in a sandbox or not.
2016-06-13 23:43:12 -04:00
Matthias Clasen
e9b6566dcc portal: Warn about unsuported modes
Bail out cleanly when we see GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
or GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER.
2016-06-11 11:01:12 -04:00
Matthias Clasen
20025b41a4 Add some docs
Mention the portal case in the general native file chooser docs,
and give some details.
2016-06-11 10:44:08 -04:00
Matthias Clasen
108ae05a76 portal: Allow forcing it off
With GTK_USE_PORTAL=1, try to use the portal even if we if we use
unsupported features. With GTK_USE_PORTAL=0, never use the portal,
as before. What we really want here is to detect the situation
of "sandboxed without full $HOME access" and use the portal in
those cases. If we make the portal smart enough to hand back
direct uris for files that the app can access, we may simplify
the check to just "sandboxed".
2016-06-11 10:23:27 -04:00
Matthias Clasen
a21156750d portal: Fall back when the portal doesn't work
Show the fallback dialog in cases where we tried to call out to
portal, but it didn't work.
2016-06-11 10:14:36 -04:00
Matthias Clasen
9b8148da17 portal: Fix error handling
We can get a D-Bus error back here, and we need to free
the reply in any case.
2016-06-11 10:11:55 -04:00
Matthias Clasen
10c3e4af91 Fix the build 2016-06-11 10:07:53 -04:00
Matthias Clasen
a97997ce3a portal: Sent more data along
Send the current_name, current_folder or current_file fields to
the portal.
2016-06-11 01:32:13 -04:00
Matthias Clasen
c7c64f0718 portal filechooser: Send filters to the portal 2016-06-11 00:46:12 -04:00
Matthias Clasen
ccadf57ee3 Add methods to serialize GtkFileFilter to a GVariant
This will be used to send filters of D-Bus in future commits.
2016-06-11 00:45:27 -04:00
Matthias Clasen
89aa77b797 portal: Implement OpenFiles and SaveFile
This fleshes out the portal file chooser some more.
2016-06-09 21:57:49 -04:00
Alexander Larsson
5be0351033 Initial version of file chooser portal support 2016-06-09 17:40:21 +02:00
8 changed files with 566 additions and 2 deletions

View File

@@ -753,6 +753,7 @@ gtk_base_c_sources = \
gtkfilechooserembed.c \
gtkfilechooserentry.c \
gtkfilechoosernative.c \
gtkfilechoosernativeportal.c \
gtkfilechooserutils.c \
gtkfilechooserwidget.c \
gtkfilefilter.c \

View File

@@ -51,7 +51,10 @@
* for use with “File/Open” or “File/Save as” commands. By default, this
* just uses a #GtkFileChooserDialog to implement the actual dialog.
* However, on certain platforms, such as Windows, the native platform
* file chooser is uses instead.
* file chooser is uses instead. When the application is running in a
* sandboxed environment without direct filesystem access (such as Flatpak),
* #GtkFileChooserNative may call the proper APIs (portals) to let the user
* choose a file and make it available to the application.
*
* While the API of #GtkFileChooserNative closely mirrors #GtkFileChooserDialog, the main
* difference is that there is no access to any #GtkWindow or #GtkWidget for the dialog.
@@ -170,11 +173,26 @@
*
* If any of these features are used the regular #GtkFileChooserDialog
* will be used in place of the native one.
*
* ## Portal details ## {#gtkfilechooserdialognative-portal}
*
* When the org.freedesktop.portal.FileChooser portal is available on the
* session bus, it is used to bring up an out-of-process file chooser. Depending
* on the kind of session the application is running in, this may or may not
* be a GTK+ file chooser. In this situation, the following things are not
* supported and will be silently ignored:
*
* * Extra widgets added with gtk_file_chooser_set_extra_widget().
*
* * Use of custom previews by connecting to #GtkFileChooser::update-preview.
*
* * Any #GtkFileFilter added with a custom filter.
*/
enum {
MODE_FALLBACK,
MODE_WIN32,
MODE_PORTAL,
};
enum {
@@ -493,6 +511,13 @@ hide_dialog (GtkFileChooserNative *self)
gtk_widget_hide (self->dialog);
}
void
gtk_file_chooser_native_show_fallback (GtkFileChooserNative *self)
{
self->mode = MODE_FALLBACK;
show_dialog (self);
}
static gboolean
gtk_file_chooser_native_set_current_folder (GtkFileChooser *chooser,
GFile *file,
@@ -558,6 +583,7 @@ gtk_file_chooser_native_get_files (GtkFileChooser *chooser)
switch (self->mode)
{
case MODE_PORTAL:
case MODE_WIN32:
return g_slist_copy_deep (self->custom_files, (GCopyFunc)g_object_ref, NULL);
@@ -579,6 +605,10 @@ gtk_file_chooser_native_show (GtkNativeDialog *native)
self->mode = MODE_WIN32;
#endif
if (self->mode == MODE_FALLBACK &&
gtk_file_chooser_native_portal_show (self))
self->mode = MODE_PORTAL;
if (self->mode == MODE_FALLBACK)
show_dialog (self);
}
@@ -597,6 +627,8 @@ gtk_file_chooser_native_hide (GtkNativeDialog *native)
#ifdef GDK_WINDOWING_WIN32
gtk_file_chooser_native_win32_hide (self);
#endif
case MODE_PORTAL:
gtk_file_chooser_native_portal_hide (self);
break;
}
}

View File

@@ -0,0 +1,387 @@
/* -*- 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 "gtkfilechooserdialog.h"
#include "gtkfilechooserprivate.h"
#include "gtkfilechooserwidget.h"
#include "gtkfilechooserwidgetprivate.h"
#include "gtkfilechooserutils.h"
#include "gtkfilechooserembed.h"
#include "gtkfilesystem.h"
#include "gtksizerequest.h"
#include "gtktypebuiltins.h"
#include "gtkintl.h"
#include "gtksettings.h"
#include "gtktogglebutton.h"
#include "gtkstylecontext.h"
#include "gtkheaderbar.h"
#include "gtklabel.h"
#include "gtkmain.h"
#include "gtkinvisible.h"
#include "gtkfilechooserentry.h"
#include "gtkfilefilterprivate.h"
#ifdef GDK_WINDOWING_X11
#include "x11/gdkx.h"
#endif
typedef struct {
GtkFileChooserNative *self;
GtkWidget *grab_widget;
GDBusConnection *connection;
char *portal_handle;
guint portal_response_signal_id;
gboolean modal;
gboolean hidden;
} FilechooserPortalData;
static void
filechooser_portal_data_free (FilechooserPortalData *data)
{
if (data->portal_response_signal_id != 0)
g_dbus_connection_signal_unsubscribe (data->connection,
data->portal_response_signal_id);
//TODO: This causes a crash in other code. Do we double unref somewhere?
// g_object_unref (data->connection);
if (data->grab_widget)
{
gtk_grab_remove (data->grab_widget);
gtk_widget_destroy (data->grab_widget);
}
if (data->self)
g_object_unref (data->self);
g_free (data->portal_handle);
g_free (data);
}
static void
file_response (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
GtkFileChooserNative *self = user_data;
FilechooserPortalData *data = self->mode_data;
guint32 portal_response;
int gtk_response;
char **uris;
int i;
GVariant *response_data;
g_variant_get (parameters, "(u^a&s@a{sv})", &portal_response, &uris, &response_data);
if (data->portal_handle == NULL ||
strcmp (object_path, data->portal_handle) != 0)
return;
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]));
switch (portal_response)
{
case 0:
gtk_response = GTK_RESPONSE_OK;
break;
case 1:
gtk_response = GTK_RESPONSE_CANCEL;
break;
case 2:
default:
gtk_response = GTK_RESPONSE_DELETE_EVENT;
break;
}
filechooser_portal_data_free (data);
self->mode_data = NULL;
_gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), gtk_response);
}
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");
g_dbus_message_set_body (message,
g_variant_new ("()"));
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);
}
}
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;
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)
_gtk_native_dialog_emit_response (GTK_NATIVE_DIALOG (self), GTK_RESPONSE_DELETE_EVENT);
g_warning ("Can't open portal file chooser: %s\n", error->message);
filechooser_portal_data_free (data);
/* fall back manually */
gtk_file_chooser_native_show_fallback (GTK_FILE_CHOOSER_NATIVE (self));
return;
}
g_variant_get_child (g_dbus_message_get_body (reply), 0, "o",
&data->portal_handle);
if (data->hidden)
{
/* The dialog was hidden before we got the handle, close it now */
send_close (data);
filechooser_portal_data_free (data);
}
g_object_unref (reply);
}
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);
}
gboolean
gtk_file_chooser_native_portal_show (GtkFileChooserNative *self)
{
FilechooserPortalData *data;
GtkWindow *transient_for;
guint update_preview_signal;
GDBusConnection *connection;
char *parent_window_str;
GDBusMessage *message;
GVariantBuilder opt_builder;
GtkFileChooserAction action;
gboolean multiple;
const char *method_name;
const char *use_portal;
gchar *path;
path = g_strdup_printf ("/run/user/%d/flatpak-info", getuid());
if (g_file_test (path, G_FILE_TEST_EXISTS))
use_portal = "1";
else
{
use_portal = g_getenv ("GTK_USE_PORTAL");
if (!use_portal)
use_portal = "";
}
g_free (path);
if (g_str_equal (use_portal, "0"))
return FALSE;
if (!g_str_equal (use_portal, "1"))
{
if (gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER (self)) != NULL)
return FALSE;
update_preview_signal = g_signal_lookup ("update-preview", GTK_TYPE_FILE_CHOOSER);
if (g_signal_has_handler_pending (self, update_preview_signal, 0, TRUE))
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));
multiple = gtk_file_chooser_get_select_multiple (GTK_FILE_CHOOSER (self));
if (action == GTK_FILE_CHOOSER_ACTION_OPEN && !multiple)
method_name = "OpenFile";
else if (action == GTK_FILE_CHOOSER_ACTION_OPEN && multiple)
method_name = "OpenFiles";
else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
method_name = "SaveFile";
else
{
g_warning ("GTK_FILE_CHOOSER_ACTION_%s is not supported by GtkFileChooserNativePortal", action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ? "SELECT_FOLDER" : "CREATE_FOLDER");
return FALSE;
}
data = g_new0 (FilechooserPortalData, 1);
data->self = g_object_ref (self);
data->connection = connection;
message = g_dbus_message_new_method_call ("org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
"org.freedesktop.portal.FileChooser",
method_name);
parent_window_str = NULL;
transient_for = gtk_native_dialog_get_transient_for (GTK_NATIVE_DIALOG (self));
if (transient_for != NULL && gtk_widget_is_visible (GTK_WIDGET (transient_for)))
{
GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (transient_for));
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_WINDOW(window))
parent_window_str = g_strdup_printf ("x11:%x", (guint32)gdk_x11_window_get_xid (window));
#endif
}
if (gtk_native_dialog_get_modal (GTK_NATIVE_DIALOG (self)))
data->modal = TRUE;
if (data->modal && transient_for)
{
data->grab_widget = gtk_invisible_new_for_screen (gtk_widget_get_screen (GTK_WIDGET (transient_for)));
gtk_grab_add (GTK_WIDGET (data->grab_widget));
}
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
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 (GTK_FILE_CHOOSER_NATIVE (self)->current_name)
g_variant_builder_add (&opt_builder, "{sv}", "current_name",
g_variant_new_string (GTK_FILE_CHOOSER_NATIVE (self)->current_name));
if (GTK_FILE_CHOOSER_NATIVE (self)->current_folder)
{
gchar *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 (GTK_FILE_CHOOSER_NATIVE (self)->current_file)
{
gchar *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);
}
g_dbus_message_set_body (message,
g_variant_new ("(ss@a{sv})",
parent_window_str ? parent_window_str : "",
gtk_native_dialog_get_title (GTK_NATIVE_DIALOG (self)),
g_variant_builder_end (&opt_builder)));
g_free (parent_window_str);
data->portal_response_signal_id =
g_dbus_connection_signal_subscribe (data->connection,
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.FileChooserRequest",
"Response",
NULL,
NULL,
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
file_response,
self, NULL);
g_dbus_connection_send_message_with_reply (data->connection,
message,
G_DBUS_SEND_MESSAGE_FLAGS_NONE,
-1,
NULL,
NULL,
open_file_msg_cb,
data);
self->mode_data = data;
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);
filechooser_portal_data_free (data);
}
self->mode_data = NULL;
}

View File

@@ -48,6 +48,11 @@ struct _GtkFileChooserNative
gboolean gtk_file_chooser_native_win32_show (GtkFileChooserNative *self);
void gtk_file_chooser_native_win32_hide (GtkFileChooserNative *self);
gboolean gtk_file_chooser_native_portal_show (GtkFileChooserNative *self);
void gtk_file_chooser_native_portal_hide (GtkFileChooserNative *self);
void gtk_file_chooser_native_show_fallback (GtkFileChooserNative *self);
G_END_DECLS
#endif /* __GTK_FILE_CHOOSER_NATIVE_PRIVATE_H__ */

View File

@@ -718,3 +718,82 @@ gtk_file_filter_filter (GtkFileFilter *filter,
return FALSE;
}
GVariant *
gtk_file_filter_to_gvariant (GtkFileFilter *filter)
{
GVariantBuilder builder;
GSList *l;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(us)"));
for (l = filter->rules; l; l = l->next)
{
FilterRule *rule = l->data;
switch (rule->type)
{
case FILTER_RULE_PATTERN:
g_variant_builder_add (&builder, "(us)", 0, rule->u.mime_type);
break;
case FILTER_RULE_MIME_TYPE:
g_variant_builder_add (&builder, "(us)", 1, rule->u.mime_type);
break;
case FILTER_RULE_PIXBUF_FORMATS:
{
GSList *f;
for (f = rule->u.pixbuf_formats; f; f = f->next)
{
GdkPixbufFormat *fmt = f->data;
gchar **mime_types;
int i;
mime_types = gdk_pixbuf_format_get_mime_types (fmt);
for (i = 0; mime_types[i]; i++)
g_variant_builder_add (&builder, "(us)", 1, mime_types[i]);
g_strfreev (mime_types);
}
}
break;
case FILTER_RULE_CUSTOM:
default:
break;
}
}
return g_variant_new ("(s@a(us))", filter->name, g_variant_builder_end (&builder));
}
GtkFileFilter *
gtk_file_filter_from_gvariant (GVariant *variant)
{
GtkFileFilter *filter;
GVariantIter *iter;
const char *name;
int type;
char *tmp;
filter = gtk_file_filter_new ();
g_variant_get (variant, "(&sa(us))", &name, &iter);
gtk_file_filter_set_name (filter, name);
while (g_variant_iter_next (iter, "(u&s)", &type, &tmp))
{
switch (type)
{
case 0:
gtk_file_filter_add_pattern (filter, tmp);
break;
case 1:
gtk_file_filter_add_mime_type (filter, tmp);
break;
default:
break;
}
}
g_variant_iter_free (iter);
return filter;
}

View File

@@ -25,6 +25,8 @@ G_BEGIN_DECLS
char ** _gtk_file_filter_get_as_patterns (GtkFileFilter *filter);
GVariant *gtk_file_filter_to_gvariant (GtkFileFilter *filter);
GtkFileFilter *gtk_file_filter_from_gvariant (GVariant *variant);
G_END_DECLS
#endif /* __GTK_FILE_FILTER_PRIVATE_H__ */

View File

@@ -24,6 +24,10 @@
#include "gtkshow.h"
#ifdef GDK_WINDOWING_X11
#include "x11/gdkx.h"
#endif
/**
* gtk_show_uri:
* @screen: (allow-none): screen to show the uri on
@@ -70,8 +74,54 @@ gtk_show_uri (GdkScreen *screen,
gdk_app_launch_context_set_screen (context, screen);
gdk_app_launch_context_set_timestamp (context, timestamp);
ret = g_app_info_launch_default_for_uri (uri, (GAppLaunchContext*)context, error);
ret = g_app_info_launch_default_for_uri (uri, G_APP_LAUNCH_CONTEXT (context), error);
g_object_unref (context);
return ret;
}
gboolean
gtk_show_uri_on_window (GtkWindow *parent,
const char *uri,
guint32 timestamp,
GError **error)
{
GdkAppLaunchContext *context;
gboolean ret;
GdkDisplay *display;
g_return_val_if_fail (uri != NULL, FALSE);
if (parent)
display = gtk_widget_get_display (GTK_WIDGET (parent));
else
display = gdk_display_get_default ();
context = gdk_display_get_app_launch_context (display);
if (parent)
{
GdkWindow *window = gtk_widget_get_window (GTK_WIDGET (parent));
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_WINDOW(window))
{
char *parent_window_str;
parent_window_str = g_strdup_printf ("x11:%x", (guint32)gdk_x11_window_get_xid (window));
g_app_launch_context_setenv (G_APP_LAUNCH_CONTEXT (context),
"PARENT_WINDOW_ID",
parent_window_str);
g_free (parent_window_str);
}
#endif
}
gdk_app_launch_context_set_timestamp (context, timestamp);
ret = g_app_info_launch_default_for_uri (uri, G_APP_LAUNCH_CONTEXT (context), error);
g_object_unref (context);
return ret;
}

View File

@@ -25,6 +25,8 @@
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkwindow.h>
G_BEGIN_DECLS
GDK_AVAILABLE_IN_ALL
@@ -33,6 +35,12 @@ gboolean gtk_show_uri (GdkScreen *screen,
guint32 timestamp,
GError **error);
GDK_AVAILABLE_IN_3_22
gboolean gtk_show_uri_on_window (GtkWindow *parent,
const char *uri,
guint32 timestamp,
GError **error);
G_END_DECLS
#endif /* __GTK_SHOW_H__ */