Merge branch 'filechooser-amberol' into 'main'

filechooser: Improve "Open in File Manager"

Closes #5260

See merge request GNOME/gtk!5140
This commit is contained in:
Matthias Clasen 2022-10-19 19:41:15 +00:00
commit 4ba5c90bc9
5 changed files with 649 additions and 6 deletions

358
gtk/gopenuriportal.c Normal file
View File

@ -0,0 +1,358 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2017 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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.1 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 <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <glib/gstdio.h>
#include "gopenuriportal.h"
#include "xdp-dbus.h"
#include "gtkwindowprivate.h"
#ifdef G_OS_UNIX
#include <gio/gunixfdlist.h>
#endif
#ifndef O_CLOEXEC
#define O_CLOEXEC 0
#else
#define HAVE_O_CLOEXEC 1
#endif
static GXdpOpenURI *openuri;
static gboolean
init_openuri_portal (void)
{
static gsize openuri_inited = 0;
if (g_once_init_enter (&openuri_inited))
{
GError *error = NULL;
GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (connection != NULL)
{
openuri = gxdp_open_uri_proxy_new_sync (connection, 0,
"org.freedesktop.portal.Desktop",
"/org/freedesktop/portal/desktop",
NULL, &error);
if (openuri == NULL)
{
g_warning ("Cannot create document portal proxy: %s", error->message);
g_error_free (error);
}
g_object_unref (connection);
}
else
{
g_warning ("Cannot connect to session bus when initializing document portal: %s",
error->message);
g_error_free (error);
}
g_once_init_leave (&openuri_inited, 1);
}
return openuri != NULL;
}
enum {
XDG_DESKTOP_PORTAL_SUCCESS = 0,
XDG_DESKTOP_PORTAL_CANCELLED = 1,
XDG_DESKTOP_PORTAL_FAILED = 2
};
static void
response_received (GDBusConnection *connection,
const char *sender_name,
const char *object_path,
const char *interface_name,
const char *signal_name,
GVariant *parameters,
gpointer user_data)
{
GTask *task = user_data;
guint32 response;
guint signal_id;
signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
g_dbus_connection_signal_unsubscribe (connection, signal_id);
g_variant_get (parameters, "(u@a{sv})", &response, NULL);
switch (response)
{
case XDG_DESKTOP_PORTAL_SUCCESS:
g_task_return_boolean (task, TRUE);
break;
case XDG_DESKTOP_PORTAL_CANCELLED:
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Launch cancelled");
break;
case XDG_DESKTOP_PORTAL_FAILED:
default:
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed");
break;
}
g_object_unref (task);
}
static void
open_call_done (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GXdpOpenURI *portal = GXDP_OPEN_URI (source);
GDBusConnection *connection;
GTask *task = user_data;
GError *error = NULL;
gboolean open_file;
gboolean res;
char *path = NULL;
const char *handle;
guint signal_id;
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (portal));
open_file = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "open-file"));
if (open_file)
res = gxdp_open_uri_call_open_file_finish (portal, &path, NULL, result, &error);
else
res = gxdp_open_uri_call_open_uri_finish (portal, &path, result, &error);
if (!res)
{
g_task_return_error (task, error);
g_object_unref (task);
g_free (path);
return;
}
handle = (const char *)g_object_get_data (G_OBJECT (task), "handle");
if (g_strcmp0 (handle, path) != 0)
{
signal_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (task), "signal-id"));
g_dbus_connection_signal_unsubscribe (connection, signal_id);
signal_id = g_dbus_connection_signal_subscribe (connection,
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Request",
"Response",
path,
NULL,
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
response_received,
task,
NULL);
g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
}
}
static void
open_uri (GFile *file,
const char *parent_window,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GVariant *opts = NULL;
int i;
guint signal_id;
if (callback)
{
GDBusConnection *connection;
GVariantBuilder opt_builder;
char *token;
char *sender;
char *handle;
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
task = g_task_new (NULL, cancellable, callback, user_data);
token = g_strdup_printf ("gtk%d", g_random_int_range (0, G_MAXINT));
sender = g_strdup (g_dbus_connection_get_unique_name (connection) + 1);
for (i = 0; sender[i]; i++)
if (sender[i] == '.')
sender[i] = '_';
handle = g_strdup_printf ("/org/freedesktop/portal/desktop/request/%s/%s", sender, token);
g_object_set_data_full (G_OBJECT (task), "handle", handle, g_free);
g_free (sender);
signal_id = g_dbus_connection_signal_subscribe (connection,
"org.freedesktop.portal.Desktop",
"org.freedesktop.portal.Request",
"Response",
handle,
NULL,
G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
response_received,
task,
NULL);
g_object_set_data (G_OBJECT (task), "signal-id", GINT_TO_POINTER (signal_id));
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);
opts = g_variant_builder_end (&opt_builder);
}
else
task = NULL;
if (g_file_is_native (file))
{
const char *path = NULL;
GUnixFDList *fd_list = NULL;
int fd, fd_id, errsv;
if (task)
g_object_set_data (G_OBJECT (task), "open-file", GINT_TO_POINTER (TRUE));
path = g_file_peek_path (file);
fd = g_open (path, O_RDONLY | O_CLOEXEC);
errsv = errno;
if (fd == -1)
{
g_task_report_new_error (NULL, callback, user_data, NULL,
G_IO_ERROR, g_io_error_from_errno (errsv),
"OpenURI portal is not available");
return;
}
#ifndef HAVE_O_CLOEXEC
fcntl (fd, F_SETFD, FD_CLOEXEC);
#endif
fd_list = g_unix_fd_list_new_from_array (&fd, 1);
fd = -1;
fd_id = 0;
gxdp_open_uri_call_open_file (openuri,
parent_window ? parent_window : "",
g_variant_new ("h", fd_id),
opts,
fd_list,
cancellable,
task ? open_call_done : NULL,
task);
g_object_unref (fd_list);
}
else
{
char *uri = g_file_get_uri (file);
gxdp_open_uri_call_open_uri (openuri,
parent_window ? parent_window : "",
uri,
opts,
cancellable,
task ? open_call_done : NULL,
task);
g_free (uri);
}
}
typedef struct {
GtkWindow *parent;
GFile *file;
GTask *task;
} OpenUriData;
static void
open_uri_data_free (OpenUriData *data)
{
if (data->parent)
gtk_window_unexport_handle (data->parent);
g_clear_object (&data->parent);
g_clear_object (&data->file);
g_clear_object (&data->task);
g_free (data);
}
static void
open_uri_done (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
OpenUriData *data = user_data;
GError *error = NULL;
if (!g_task_propagate_boolean (G_TASK (result), &error))
g_task_return_error (data->task, error);
else
g_task_return_boolean (data->task, TRUE);
open_uri_data_free (data);
}
static void
window_handle_exported (GtkWindow *window,
const char *handle,
gpointer user_data)
{
OpenUriData *data = user_data;
open_uri (data->file, handle, g_task_get_cancellable (data->task), open_uri_done, data);
}
void
g_openuri_portal_open_async (GFile *file,
GtkWindow *parent,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
OpenUriData *data;
if (!init_openuri_portal ())
{
g_task_report_new_error (NULL, callback, user_data, NULL,
G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED,
"OpenURI portal is not available");
return;
}
data = g_new0 (OpenUriData, 1);
data->parent = parent ? g_object_ref (parent) : NULL;
data->file = g_object_ref (file);
data->task = g_task_new (parent, cancellable, callback, user_data);
g_task_set_source_tag (data->task, g_openuri_portal_open_async);
if (!parent || !gtk_window_export_handle (parent, window_handle_exported, data))
window_handle_exported (parent, NULL, data);
}
gboolean
g_openuri_portal_open_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}

40
gtk/gopenuriportal.h Normal file
View File

@ -0,0 +1,40 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2017 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* 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.1 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/>.
*/
#ifndef __G_OPEN_URI_PORTAL_H__
#include "gtkwindow.h"
#include <glib.h>
#include <gio/gio.h>
G_BEGIN_DECLS
void g_openuri_portal_open_async (GFile *file,
GtkWindow *window,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean g_openuri_portal_open_finish (GAsyncResult *result,
GError **error);
G_END_DECLS
#endif

View File

@ -87,6 +87,10 @@
#include "gtkshortcut.h"
#include "gtkstringlist.h"
#ifndef G_OS_WIN32
#include "gopenuriportal.h"
#endif
#include <cairo-gobject.h>
#ifdef HAVE_UNISTD_H
@ -1442,6 +1446,10 @@ visit_file_cb (GSimpleAction *action,
g_slist_free_full (files, g_object_unref);
}
#define FILE_MANAGER_DBUS_NAME "org.freedesktop.FileManager1"
#define FILE_MANAGER_DBUS_IFACE "org.freedesktop.FileManager1"
#define FILE_MANAGER_DBUS_PATH "/org/freedesktop/FileManager1"
/* Callback used when the "Open this folder" menu item is activated */
static void
open_folder_cb (GSimpleAction *action,
@ -1449,22 +1457,78 @@ open_folder_cb (GSimpleAction *action,
gpointer data)
{
GtkFileChooserWidget *impl = data;
GtkWidget *toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (impl)));
GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (impl));
GtkWindow *toplevel = GTK_IS_WINDOW (root) ? GTK_WINDOW (root) : NULL;
GSList *files;
GFile *file;
char *uri;
files = get_selected_files (impl);
if (!files)
return;
/* Sigh, just use the first one */
if (files && GTK_IS_WINDOW (toplevel))
file = files->data;
#ifdef G_OS_WIN32
uri = g_file_get_uri (file);
gtk_show_uri (toplevel, uri, GDK_CURRENT_TIME);
g_free (uri);
#else
if (gdk_should_use_portal ())
{
GFile *file = files->data;
char *uri;
g_openuri_portal_open_async (file, toplevel, NULL, NULL, NULL);
}
else
{
GDBusConnection *bus;
GVariantBuilder *uris_builder;
GVariant *result;
GError *error = NULL;
uri = g_file_get_uri (file);
gtk_show_uri (GTK_WINDOW (toplevel), uri, GDK_CURRENT_TIME);
bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
uris_builder = g_variant_builder_new (G_VARIANT_TYPE ("as"));
g_variant_builder_add (uris_builder, "s", uri);
result = g_dbus_connection_call_sync (bus,
FILE_MANAGER_DBUS_NAME,
FILE_MANAGER_DBUS_PATH,
FILE_MANAGER_DBUS_IFACE,
"ShowFolders",
g_variant_new ("(ass)", uris_builder, ""),
NULL, /* ignore returned type */
G_DBUS_CALL_FLAGS_NONE,
-1,
NULL,
&error);
if (error)
{
if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_NAME_HAS_NO_OWNER) ||
g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN))
g_debug ("No " FILE_MANAGER_DBUS_NAME " available");
else
g_warning ("Failed to call ShowFolders: %s", error->message);
g_error_free (error);
}
if (result)
g_variant_unref (result);
else
gtk_show_uri (toplevel, uri, GDK_CURRENT_TIME);
g_free (uri);
}
#endif
g_slist_free_full (files, g_object_unref);
}

View File

@ -766,6 +766,10 @@ if not (x11_enabled or win32_enabled)
gtk_sources += ['gtkmountoperation-stub.c', ]
endif
if not os_win32
gtk_sources += ['gopenuriportal.c', ]
endif
gen_gtk_gresources_xml = find_program('gen-gtk-gresources-xml.py')
gtk_gresources_xml = configure_file(output: 'gtk.gresources.xml',
command: [
@ -997,6 +1001,16 @@ typefuncs = custom_target('gtktypefuncs.inc',
install: false,
)
if os_win32
xdp_dbus_generated = []
else
xdp_dbus_generated = gnome.gdbus_codegen('xdp-dbus',
sources : 'org.freedesktop.portal.OpenURI.xml',
interface_prefix : 'org.freedesktop.portal.',
namespace : 'GXdp',
)
endif
gtkversion_cdata = configuration_data()
gtkversion_cdata.set('GTK_MAJOR_VERSION', gtk_major_version)
gtkversion_cdata.set('GTK_MINOR_VERSION', gtk_minor_version)
@ -1156,7 +1170,7 @@ darwin_versions = [
# Library
libgtk_static = static_library('gtk',
sources: [typefuncs, gtk_sources, gtkmarshal_h, gtkprivatetypebuiltins_h],
sources: [typefuncs, gtk_sources, gtkmarshal_h, gtkprivatetypebuiltins_h, xdp_dbus_generated],
c_args: gtk_cargs + common_cflags,
include_directories: [confinc, gdkinc, gskinc, gtkinc],
dependencies: gtk_deps + [libgtk_css_dep, libgdk_dep, libgsk_dep],

View File

@ -0,0 +1,167 @@
<?xml version="1.0"?>
<!--
Copyright (C) 2016 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/>.
Author: Matthias Clasen <mclasen@redhat.com>
-->
<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<!--
org.freedesktop.portal.OpenURI:
@short_description: Portal for opening URIs
The OpenURI portal allows sandboxed applications to open
URIs (e.g. a http: link to the applications homepage)
under the control of the user.
This documentation describes version 3 of this interface.
-->
<interface name="org.freedesktop.portal.OpenURI">
<!--
OpenURI:
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link>
@uri: The uri to open
@options: Vardict with optional further onformation
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Asks to open a uri.
Note that file:// uris are explicitly not supported by this method.
To request opening local files, use org.freedesktop.portal.OpenURI.OpenFile().
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>handle_token s</term>
<listitem><para>
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the #org.freedesktop.portal.Request documentation for
more information about the @handle.
</para></listitem>
</varlistentry>
<varlistentry>
<term>writable b</term>
<listitem><para>
Whether to allow the chosen application to write to the file.
</para><para>
This key only takes effect the uri points to a local file that
is exported in the document portal, and the chosen application
is sandboxed itself.
</para></listitem>
</varlistentry>
<varlistentry>
<term>ask b</term>
<listitem><para>
Whether to ask the user to choose an app. If this is not passed, or false,
the portal may use a default or pick the last choice.
</para><para>
The ask option was introduced in version 3 of the interface.
</para></listitem>
</varlistentry>
</variablelist>
-->
<method name="OpenURI">
<arg type="s" name="parent_window" direction="in"/>
<arg type="s" name="uri" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
OpenFile:
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link>
@fd: File descriptor for the file to open
@options: Vardict with optional further onformation
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Asks to open a local file.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>handle_token s</term>
<listitem><para>
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the #org.freedesktop.portal.Request documentation for
more information about the @handle.
</para></listitem>
</varlistentry>
<varlistentry>
<term>writable b</term>
<listitem><para>
Whether to allow the chosen application to write to the file.
</para><para>
This key only takes effect the uri points to a local file that
is exported in the document portal, and the chosen application
is sandboxed itself.
</para></listitem>
</varlistentry>
<varlistentry>
<term>ask b</term>
<listitem><para>
Whether to ask the user to choose an app. If this is not passed, or false,
the portal may use a default or pick the last choice.
</para><para>
The ask option was introduced in version 3 of the interface.
</para></listitem>
</varlistentry>
</variablelist>
The OpenFile method was introduced in version 2 of the OpenURI portal API.
-->
<method name="OpenFile">
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="s" name="parent_window" direction="in"/>
<arg type="h" name="fd" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="o" name="handle" direction="out"/>
</method>
<!--
OpenDirectory:
@parent_window: Identifier for the application window, see <link linkend="parent_window">Common Conventions</link>
@fd: File descriptor for a file
@options: Vardict with optional further onformation
@handle: Object path for the #org.freedesktop.portal.Request object representing this call
Asks to open the directory containing a local file in the file browser.
Supported keys in the @options vardict include:
<variablelist>
<varlistentry>
<term>handle_token s</term>
<listitem><para>
A string that will be used as the last element of the @handle. Must be a valid
object path element. See the #org.freedesktop.portal.Request documentation for
more information about the @handle.
</para></listitem>
</varlistentry>
</variablelist>
The OpenDirectory method was introduced in version 3 of the OpenURI portal API.
-->
<method name="OpenDirectory">
<annotation name="org.gtk.GDBus.C.UnixFD" value="true"/>
<arg type="s" name="parent_window" direction="in"/>
<arg type="h" name="fd" direction="in"/>
<arg type="a{sv}" name="options" direction="in"/>
<arg type="o" name="handle" direction="out"/>
</method>
<property name="version" type="u" access="read"/>
</interface>
</node>