Use OpenFile for local files

The OpenURI portal has a separate method to handle local
files now. Use it. At the same time, split out the openuri
helpers into separate files, and generate code for the
OpenURI portal.
This commit is contained in:
Matthias Clasen 2017-05-20 16:21:14 -04:00
parent b5e8e4eea9
commit 0bc386c4cb
5 changed files with 456 additions and 245 deletions

View File

@ -281,6 +281,8 @@ unix_sources = \
gportalnotificationbackend.c \
gdocumentportal.c \
gdocumentportal.h \
gopenuriportal.c \
gopenuriportal.h \
gportalsupport.c \
gportalsupport.h \
$(portal_sources) \
@ -368,6 +370,7 @@ CLEANFILES += $(xdp_dbus_built_sources)
portal_interfaces = \
org.freedesktop.portal.Documents.xml \
org.freedesktop.portal.OpenURI.xml \
org.freedesktop.portal.NetworkMonitor.xml \
org.freedesktop.portal.ProxyResolver.xml \
$(NULL)
@ -383,6 +386,7 @@ $(xdp_dbus_built_sources) : $(portal_interfaces)
--generate-c-code $(builddir)/xdp-dbus \
--annotate "org.freedesktop.portal.Documents.Add()" "org.gtk.GDBus.C.UnixFD" "true" \
--annotate "org.freedesktop.portal.Documents.AddNamed()" "org.gtk.GDBus.C.UnixFD" "true" \
--annotate "org.freedesktop.portal.OpenURI.OpenFile()" "org.gtk.GDBus.C.UnixFD" "true" \
$^
portal_sources = \

View File

@ -32,15 +32,12 @@
#ifdef G_OS_UNIX
#include "gdbusconnection.h"
#include "gdbusmessage.h"
#include "gdocumentportal.h"
#include "gportalsupport.h"
#endif
#ifdef G_OS_UNIX
#define FLATPAK_OPENURI_PORTAL_BUS_NAME "org.freedesktop.portal.Desktop"
#define FLATPAK_OPENURI_PORTAL_PATH "/org/freedesktop/portal/desktop"
#define FLATPAK_OPENURI_PORTAL_IFACE "org.freedesktop.portal.OpenURI"
#define FLATPAK_OPENURI_PORTAL_METHOD "OpenURI"
#include "gunixfdlist.h"
#include "gopenuriportal.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
/**
@ -687,240 +684,6 @@ g_app_info_should_show (GAppInfo *appinfo)
return (* iface->should_show) (appinfo);
}
#ifdef G_OS_UNIX
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);
if (response == 0)
g_task_return_boolean (task, TRUE);
else if (response == 1)
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Launch cancelled");
else
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed");
g_object_unref (task);
}
static void
open_uri_done (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection *connection = G_DBUS_CONNECTION (source);
GTask *task = user_data;
GVariant *res;
GError *error = NULL;
const char *path;
guint signal_id;
res = g_dbus_connection_call_finish (connection, result, &error);
if (res == NULL)
{
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_variant_get (res, "(&o)", &path);
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));
g_variant_unref (res);
}
static char *
real_uri_for_portal (const char *uri,
GAppLaunchContext *context,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data,
GError **error)
{
GFile *file = NULL;
char *real_uri = NULL;
file = g_file_new_for_uri (uri);
if (g_file_is_native (file))
{
real_uri = g_document_portal_add_document (file, error);
g_object_unref (file);
if (real_uri == NULL)
{
g_task_report_error (context, callback, user_data, NULL, *error);
return NULL;
}
}
else
{
g_object_unref (file);
real_uri = g_strdup (uri);
}
return real_uri;
}
static void
launch_default_with_portal_async (const char *uri,
GAppLaunchContext *context,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GDBusConnection *session_bus;
GVariantBuilder opt_builder;
const char *parent_window = NULL;
char *real_uri;
GTask *task;
GAsyncReadyCallback dbus_callback;
GError *error = NULL;
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (session_bus == NULL)
{
g_task_report_error (context, callback, user_data, NULL, error);
return;
}
if (context && context->priv->envp)
parent_window = g_environ_getenv (context->priv->envp, "PARENT_WINDOW_ID");
real_uri = real_uri_for_portal (uri, context, cancellable, callback, user_data, &error);
if (real_uri == NULL)
{
g_object_unref (session_bus);
return;
}
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
if (callback)
{
task = g_task_new (context, cancellable, callback, user_data);
dbus_callback = open_uri_done;
}
else
{
task = NULL;
dbus_callback = NULL;
}
g_dbus_connection_call (session_bus,
FLATPAK_OPENURI_PORTAL_BUS_NAME,
FLATPAK_OPENURI_PORTAL_PATH,
FLATPAK_OPENURI_PORTAL_IFACE,
FLATPAK_OPENURI_PORTAL_METHOD,
g_variant_new ("(ss@a{sv})",
parent_window ? parent_window : "",
real_uri,
g_variant_builder_end (&opt_builder)),
NULL,
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
cancellable,
dbus_callback,
task);
g_dbus_connection_flush (session_bus, cancellable, NULL, NULL);
g_object_unref (session_bus);
g_free (real_uri);
}
static void
launch_default_with_portal_sync (const char *uri,
GAppLaunchContext *context)
{
GDBusConnection *session_bus;
GVariantBuilder opt_builder;
GVariant *res = NULL;
const char *parent_window = NULL;
char *real_uri;
GError *error = NULL;
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (session_bus == NULL)
{
g_task_report_error (context, NULL, NULL, NULL, error);
return;
}
if (context && context->priv->envp)
parent_window = g_environ_getenv (context->priv->envp, "PARENT_WINDOW_ID");
real_uri = real_uri_for_portal (uri, context, NULL, NULL, NULL, &error);
if (real_uri == NULL)
{
g_object_unref (session_bus);
return;
}
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
/* Calling the D-Bus method for the OpenURI portal "protects" the logic from
* not ever having the remote method running in case the xdg-desktop-portal
* process is not yet running and the caller quits quickly after the call.
*/
res = g_dbus_connection_call_sync (session_bus,
FLATPAK_OPENURI_PORTAL_BUS_NAME,
FLATPAK_OPENURI_PORTAL_PATH,
FLATPAK_OPENURI_PORTAL_IFACE,
FLATPAK_OPENURI_PORTAL_METHOD,
g_variant_new ("(ss@a{sv})",
parent_window ? parent_window : "",
real_uri,
g_variant_builder_end (&opt_builder)),
NULL,
G_DBUS_CALL_FLAGS_NONE,
G_MAXINT,
NULL,
&error);
if (res == NULL)
g_task_report_error (context, NULL, NULL, NULL, error);
else
g_variant_unref (res);
g_dbus_connection_flush (session_bus, NULL, NULL, NULL);
g_object_unref (session_bus);
g_free (real_uri);
}
static gboolean
launch_default_with_portal (const char *uri,
GAppLaunchContext *context,
GError **error)
{
launch_default_with_portal_sync (uri, context);
return TRUE;
}
#endif
static gboolean
launch_default_for_uri (const char *uri,
GAppLaunchContext *context,
@ -985,10 +748,16 @@ g_app_info_launch_default_for_uri (const char *uri,
#ifdef G_OS_UNIX
if (glib_should_use_portal ())
{
const char *parent_window = NULL;
/* Reset any error previously set by launch_default_for_uri */
g_clear_error (error);
return launch_default_with_portal (uri, launch_context, error);
if (launch_context && launch_context->priv->envp)
parent_window = g_environ_getenv (launch_context->priv->envp, "PARENT_WINDOW_ID");
return g_openuri_portal_open_uri (uri, parent_window, error);
}
#endif
@ -1028,7 +797,12 @@ g_app_info_launch_default_for_uri_async (const char *uri,
#ifdef G_OS_UNIX
if (!res && glib_should_use_portal ())
{
launch_default_with_portal_async (uri, context, cancellable, callback, user_data);
const char *parent_window = NULL;
if (context && context->priv->envp)
parent_window = g_environ_getenv (context->priv->envp, "PARENT_WINDOW_ID");
g_openuri_portal_open_uri_async (uri, parent_window, cancellable, callback, user_data);
return;
}
#endif
@ -1057,7 +831,7 @@ gboolean
g_app_info_launch_default_for_uri_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
return g_openuri_portal_open_uri_finish (result, error);
}
/**

287
gio/gopenuriportal.c Normal file
View File

@ -0,0 +1,287 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2017 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 <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "gopenuriportal.h"
#include "xdp-dbus.h"
#include "gstdio.h"
#ifdef G_OS_UNIX
#include "gunixfdlist.h"
#endif
#ifndef O_PATH
#define O_PATH 0
#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;
}
gboolean
g_openuri_portal_open_uri (const char *uri,
const char *parent_window,
GError **error)
{
GFile *file = NULL;
GVariantBuilder opt_builder;
gboolean res;
if (!init_openuri_portal ())
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
"OpenURI portal is not available");
return FALSE;
}
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
file = g_file_new_for_uri (uri);
if (g_file_is_native (file))
{
char *path;
GUnixFDList *fd_list;
int fd, fd_id;
path = g_file_get_path (file);
fd = open (path, O_PATH | O_CLOEXEC);
#ifndef HAVE_O_CLOEXEC
fcntl (fd, F_SETFD, FD_CLOEXEC);
#endif
fd_list = g_unix_fd_list_new_from_array (&fd, 1);
fd_id = 0;
res = gxdp_open_uri_call_open_file_sync (openuri,
parent_window ? parent_window : "",
g_variant_new ("h", fd_id),
g_variant_builder_end (&opt_builder),
fd_list,
NULL,
NULL,
NULL,
error);
g_free (path);
g_object_unref (fd_list);
}
else
{
res = gxdp_open_uri_call_open_uri_sync (openuri,
parent_window ? parent_window : "",
uri,
g_variant_builder_end (&opt_builder),
NULL,
NULL,
error);
}
g_object_unref (file);
return res;
}
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);
if (response == 0)
g_task_return_boolean (task, TRUE);
else if (response == 1)
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Launch cancelled");
else
g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED, "Launch failed");
g_object_unref (task);
}
static void
open_call_done (GObject *source,
GAsyncResult *result,
gpointer user_data)
{
GDBusConnection *connection = G_DBUS_CONNECTION (source);
GTask *task = user_data;
GError *error = NULL;
gboolean open_file;
gboolean res;
char *path;
guint signal_id;
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 (openuri, &path, NULL, result, &error);
else
res = gxdp_open_uri_call_open_uri_finish (openuri, &path, result, &error);
if (!res)
{
g_task_return_error (task, error);
g_object_unref (task);
g_free (path);
return;
}
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));
}
void
g_openuri_portal_open_uri_async (const char *uri,
const char *parent_window,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
GFile *file;
GVariantBuilder opt_builder;
if (!init_openuri_portal ())
{
GError *error = NULL;
g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED, "OpenURI portal is not available");
g_task_report_error (NULL, callback, user_data, NULL, error);
return;
}
if (callback)
task = g_task_new (NULL, cancellable, callback, user_data);
else
task = NULL;
g_variant_builder_init (&opt_builder, G_VARIANT_TYPE_VARDICT);
file = g_file_new_for_uri (uri);
if (g_file_is_native (file))
{
char *path;
GUnixFDList *fd_list;
int fd, fd_id;
if (task)
g_object_set_data (G_OBJECT (task), "open-file", GINT_TO_POINTER (TRUE));
path = g_file_get_path (file);
fd = open (path, O_PATH | O_CLOEXEC);
#ifndef HAVE_O_CLOEXEC
fcntl (fd, F_SETFD, FD_CLOEXEC);
#endif
fd_list = g_unix_fd_list_new_from_array (&fd, 1);
fd_id = 0;
gxdp_open_uri_call_open_file (openuri,
parent_window ? parent_window : "",
g_variant_new ("h", fd_id),
g_variant_builder_end (&opt_builder),
fd_list,
cancellable,
task ? open_call_done : NULL,
task);
g_object_unref (fd_list);
g_free (path);
}
else
{
gxdp_open_uri_call_open_uri (openuri,
parent_window ? parent_window : "",
uri,
g_variant_builder_end (&opt_builder),
cancellable,
task ? open_call_done : NULL,
task);
}
g_object_unref (file);
}
gboolean
g_openuri_portal_open_uri_finish (GAsyncResult *result,
GError **error)
{
return g_task_propagate_boolean (G_TASK (result), error);
}

41
gio/gopenuriportal.h Normal file
View File

@ -0,0 +1,41 @@
/* GIO - GLib Input, Output and Streaming Library
*
* Copyright 2017 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/>.
*/
#ifndef __G_OPEN_URI_PORTAL_H__
#include <glib.h>
#include <gio/gio.h>
G_BEGIN_DECLS
gboolean g_openuri_portal_open_uri (const char *uri,
const char *parent_window,
GError **error);
void g_openuri_portal_open_uri_async (const char *uri,
const char *parent_window,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean g_openuri_portal_open_uri_finish (GAsyncResult *result,
GError **error);
G_END_DECLS
#endif

View File

@ -0,0 +1,105 @@
<?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.
-->
<interface name="org.freedesktop.portal.OpenURI">
<!--
OpenURI:
@parent_window: Identifier for the application window
@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.
The @parent_window identifier must be of the form "x11:$XID" for an X11
window. Support for other window systems may be added in the future.
Note that file:// uris are explicitly not supported by this method.
To request opening local files, use org.freedesktop.portal.OpenFile().
Supported keys in the @options vardict include:
<variablelist>
<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>
</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
@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.
The @parent_window identifier must be of the form "x11:$XID" for an X11
window. Support for other window systems may be added in the future.
Supported keys in the @options vardict include:
<variablelist>
<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>
</variablelist>
The OpenFile method was introduced in version 2 of the OpenURI portal API.
-->
<method name="OpenFile">
<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>