Merge branch 'uri-launcher' into 'main'

filelauncher: Plug a memory leak

See merge request GNOME/gtk!5425
This commit is contained in:
Matthias Clasen
2023-01-14 22:59:01 +00:00
9 changed files with 480 additions and 17 deletions

View File

@@ -105,3 +105,7 @@ retire it. If you need such a widget, it is relatively trivial to create one
using a [class@Gtk.Revealer] with labels and buttons.
Other libraries, such as libadwaita, may provide replacements as well.
## gtk_show_uri is being replaced
Instead of gtk_show_uri(), you should use GtkUriLauncher or GtkFileLauncher.

View File

@@ -99,7 +99,8 @@ window_handle_exported (GtkWindow *window,
* This is the recommended call to be used as it passes information
* necessary for sandbox helpers to parent their dialogs properly.
*
* Deprecated: 4.10: Use [method@Gtk.FileLauncher.launch] instead
* Deprecated: 4.10: Use [method@Gtk.FileLauncher.launch] or
* [method@Gtk.UriLauncher.launch] instead
*/
void
gtk_show_uri_full (GtkWindow *parent,
@@ -147,7 +148,8 @@ gtk_show_uri_full (GtkWindow *parent,
* Returns: %TRUE if the URI was shown successfully.
* Otherwise, %FALSE is returned and @error is set
*
* Deprecated: 4.10: Use [method@Gtk.FileLauncher.launch_finish] instead
* Deprecated: 4.10: Use [method@Gtk.FileLauncher.launch_finish] or
* [method@Gtk.UriLauncher.launch_finish] instead
*/
gboolean
gtk_show_uri_full_finish (GtkWindow *parent,
@@ -191,7 +193,8 @@ show_uri_done (GObject *object,
* This function launches the default application for showing
* a given uri, or shows an error dialog if that fails.
*
* Deprecated: 4.10: Use [method@Gtk.FileLauncher.launch] instead
* Deprecated: 4.10: Use [method@Gtk.FileLauncher.launch] or
* [method@Gtk.UriLauncher.launch] instead
*/
void
gtk_show_uri (GtkWindow *parent,

View File

@@ -103,6 +103,7 @@ enum {
typedef struct {
GtkWindow *parent;
GFile *file;
char *uri;
gboolean open_folder;
GDBusConnection *connection;
GCancellable *cancellable;
@@ -125,6 +126,7 @@ open_uri_data_free (OpenUriData *data)
gtk_window_unexport_handle (data->parent);
g_clear_object (&data->parent);
g_clear_object (&data->file);
g_free (data->uri);
g_clear_object (&data->cancellable);
g_clear_object (&data->task);
g_free (data->handle);
@@ -258,14 +260,13 @@ canceled (GCancellable *cancellable,
}
static void
open_uri (GFile *file,
gboolean open_folder,
open_uri (OpenUriData *data,
const char *parent_window,
const char *activation_token,
GAsyncReadyCallback callback,
gpointer user_data)
GAsyncReadyCallback callback)
{
OpenUriData *data = user_data;
GFile *file = data->file;
gboolean open_folder = data->open_folder;
GTask *task;
GVariant *opts = NULL;
int i;
@@ -277,9 +278,9 @@ open_uri (GFile *file,
connection = g_dbus_proxy_get_connection (G_DBUS_PROXY (openuri));
data->connection = g_object_ref (connection);
task = g_task_new (NULL, NULL, callback, user_data);
task = g_task_new (NULL, NULL, callback, data);
g_task_set_check_cancellable (task, FALSE);
g_task_set_task_data (task, user_data, NULL);
g_task_set_task_data (task, data, NULL);
if (data->cancellable)
data->cancel_handler = g_signal_connect (data->cancellable, "cancelled", G_CALLBACK (canceled), task);
@@ -312,7 +313,7 @@ open_uri (GFile *file,
opts = g_variant_builder_end (&opt_builder);
if (g_file_is_native (file))
if (file && g_file_is_native (file))
{
const char *path = NULL;
GUnixFDList *fd_list = NULL;
@@ -366,12 +367,15 @@ open_uri (GFile *file,
}
else
{
char *uri = g_file_get_uri (file);
char *uri = NULL;
if (file)
uri = g_file_get_uri (file);
data->call = OPEN_URI;
gxdp_open_uri_call_open_uri (openuri,
parent_window ? parent_window : "",
uri,
uri ? uri : data->uri,
opts,
NULL,
open_call_done,
@@ -431,7 +435,7 @@ window_handle_exported (GtkWindow *window,
activation_token = G_APP_LAUNCH_CONTEXT_GET_CLASS (context)->get_startup_notify_id (context, NULL, NULL);
g_object_unref (context);
open_uri (data->file, data->open_folder, handle, activation_token, open_uri_done, data);
open_uri (data, handle, activation_token, open_uri_done);
g_free (activation_token);
}
@@ -471,5 +475,45 @@ gboolean
g_openuri_portal_open_finish (GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == g_openuri_portal_open_async, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
void
g_openuri_portal_open_uri_async (const char *uri,
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,
GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED,
"The OpenURI portal is not available");
return;
}
data = g_new0 (OpenUriData, 1);
data->parent = parent ? g_object_ref (parent) : NULL;
data->uri = g_strdup (uri);
data->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
data->task = g_task_new (parent, cancellable, callback, user_data);
g_task_set_check_cancellable (data->task, FALSE);
g_task_set_source_tag (data->task, g_openuri_portal_open_uri_async);
if (!parent || !gtk_window_export_handle (parent, window_handle_exported, data))
window_handle_exported (parent, NULL, data);
}
gboolean
g_openuri_portal_open_uri_finish (GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == g_openuri_portal_open_uri_async, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}

View File

@@ -38,6 +38,15 @@ void g_openuri_portal_open_async (GFile *file,
gboolean g_openuri_portal_open_finish (GAsyncResult *result,
GError **error);
void g_openuri_portal_open_uri_async (const char *uri,
GtkWindow *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

@@ -286,6 +286,7 @@
#include <gtk/deprecated/gtktreeviewcolumn.h>
#include <gtk/gtktypebuiltins.h>
#include <gtk/gtktypes.h>
#include <gtk/gtkurilauncher.h>
#include <gtk/gtkversion.h>
#include <gtk/gtkvideo.h>
#include <gtk/gtkviewport.h>

View File

@@ -29,8 +29,8 @@
/**
* GtkFileLauncher:
*
* A `GtkFileLauncher` object collects the arguments that are needed to open a uri
* with an application.
* A `GtkFileLauncher` object collects the arguments that are needed to open a
* file with an application.
*
* Depending on system configuration, user preferences and available APIs, this
* may or may not show an app chooser dialog or launch the default application
@@ -40,6 +40,8 @@
* This API follows the GIO async pattern, and the result can be obtained by
* calling [method@Gtk.FileLauncher.launch_finish].
*
* To launch uris that don't represent files, use [class@Gtk.UriLauncher].
*
* Since: 4.10
*/
@@ -70,7 +72,9 @@ gtk_file_launcher_init (GtkFileLauncher *self)
static void
gtk_file_launcher_finalize (GObject *object)
{
//GtkFileLauncher *self = GTK_FILE_LAUNCHER (object);
GtkFileLauncher *self = GTK_FILE_LAUNCHER (object);
g_clear_object (&self->file);
G_OBJECT_CLASS (gtk_file_launcher_parent_class)->finalize (object);
}

339
gtk/gtkurilauncher.c Normal file
View File

@@ -0,0 +1,339 @@
/*
* GTK - The GIMP Toolkit
* Copyright (C) 2022 Red Hat, Inc.
* All rights reserved.
*
* This Library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gtkurilauncher.h"
#include "gtkdialogerror.h"
#include "gopenuriportal.h"
#include "deprecated/gtkshow.h"
#include <glib/gi18n-lib.h>
/**
* GtkUriLauncher:
*
* A `GtkUriLauncher` object collects the arguments that are needed to open a uri
* with an application.
*
* Depending on system configuration, user preferences and available APIs, this
* may or may not show an app chooser dialog or launch the default application
* right away.
*
* The operation is started with the [method@Gtk.UriLauncher.launch] function.
* This API follows the GIO async pattern, and the result can be obtained by
* calling [method@Gtk.UriLauncher.launch_finish].
*
* To launch a file, use [class@Gtk.FileLauncher].
*
* Since: 4.10
*/
/* {{{ GObject implementation */
struct _GtkUriLauncher
{
GObject parent_instance;
char *uri;
};
enum {
PROP_URI = 1,
NUM_PROPERTIES
};
static GParamSpec *properties[NUM_PROPERTIES];
G_DEFINE_TYPE (GtkUriLauncher, gtk_uri_launcher, G_TYPE_OBJECT)
static void
gtk_uri_launcher_init (GtkUriLauncher *self)
{
}
static void
gtk_uri_launcher_finalize (GObject *object)
{
GtkUriLauncher *self = GTK_URI_LAUNCHER (object);
g_free (self->uri);
G_OBJECT_CLASS (gtk_uri_launcher_parent_class)->finalize (object);
}
static void
gtk_uri_launcher_get_property (GObject *object,
unsigned int property_id,
GValue *value,
GParamSpec *pspec)
{
GtkUriLauncher *self = GTK_URI_LAUNCHER (object);
switch (property_id)
{
case PROP_URI:
g_value_set_string (value, self->uri);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_uri_launcher_set_property (GObject *object,
unsigned int property_id,
const GValue *value,
GParamSpec *pspec)
{
GtkUriLauncher *self = GTK_URI_LAUNCHER (object);
switch (property_id)
{
case PROP_URI:
gtk_uri_launcher_set_uri (self, g_value_get_string (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_uri_launcher_class_init (GtkUriLauncherClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
object_class->finalize = gtk_uri_launcher_finalize;
object_class->get_property = gtk_uri_launcher_get_property;
object_class->set_property = gtk_uri_launcher_set_property;
/**
* GtkUriLauncher:uri: (attributes org.gtk.Property.get=gtk_uri_launcher_get_uri org.gtk.Property.set=gtk_uri_launcher_set_uri)
*
* The uri to launch.
*
* Since: 4.10
*/
properties[PROP_URI] =
g_param_spec_string ("uri", NULL, NULL,
NULL,
G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
g_object_class_install_properties (object_class, NUM_PROPERTIES, properties);
}
/* }}} */
/* {{{ API: Constructor */
/**
* gtk_uri_launcher_new:
* @uri: (nullable): the uri to open
*
* Creates a new `GtkUriLauncher` object.
*
* Returns: the new `GtkUriLauncher`
*
* Since: 4.10
*/
GtkUriLauncher *
gtk_uri_launcher_new (const char *uri)
{
return g_object_new (GTK_TYPE_URI_LAUNCHER,
"uri", uri,
NULL);
}
/* }}} */
/* {{{ API: Getters and setters */
/**
* gtk_uri_launcher_get_uri:
* @self: a `GtkUriLauncher`
*
* Gets the uri that will be opened.
*
* Returns: (transfer none) (nullable): the uri
*
* Since: 4.10
*/
const char *
gtk_uri_launcher_get_uri (GtkUriLauncher *self)
{
g_return_val_if_fail (GTK_IS_URI_LAUNCHER (self), NULL);
return self->uri;
}
/**
* gtk_uri_launcher_set_uri:
* @self: a `GtkUriLauncher`
* @uri: (nullable): the uri
*
* Sets the uri that will be opened.
*
* Since: 4.10
*/
void
gtk_uri_launcher_set_uri (GtkUriLauncher *self,
const char *uri)
{
g_return_if_fail (GTK_IS_URI_LAUNCHER (self));
if (g_strcmp0 (self->uri, uri) == 0)
return;
g_free (self->uri);
self->uri = g_strdup (uri);
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_URI]);
}
/* }}} */
/* {{{ Async implementation */
#ifndef G_OS_WIN32
static void
open_done (GObject *source,
GAsyncResult *result,
gpointer data)
{
GTask *task = G_TASK (data);
GError *error = NULL;
if (!g_openuri_portal_open_finish (result, &error))
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
#endif
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
static void
show_uri_done (GObject *source,
GAsyncResult *result,
gpointer data)
{
GtkWindow *parent = GTK_WINDOW (source);
GTask *task = G_TASK (data);
GError *error = NULL;
if (!gtk_show_uri_full_finish (parent, result, &error))
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_CANCELLED, "Cancelled by user");
else
g_task_return_new_error (task, GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED, "%s", error->message);
g_error_free (error);
}
else
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
G_GNUC_END_IGNORE_DEPRECATIONS
/* }}} */
/* {{{ Async API */
/**
* gtk_uri_launcher_launch:
* @self: a `GtkUriLauncher`
* @parent: (nullable): the parent `GtkWindow`
* @cancellable: (nullable): a `GCancellable` to cancel the operation
* @callback: (scope async): a callback to call when the operation is complete
* @user_data: (closure callback): data to pass to @callback
*
* Launch an application to open the uri.
*
* This may present an app chooser dialog to the user.
*
* The @callback will be called when the operation is completed.
* It should call [method@Gtk.UriLauncher.launch_finish] to obtain
* the result.
*
* Since: 4.10
*/
void
gtk_uri_launcher_launch (GtkUriLauncher *self,
GtkWindow *parent,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_return_if_fail (GTK_IS_URI_LAUNCHER (self));
task = g_task_new (self, cancellable, callback, user_data);
g_task_set_check_cancellable (task, FALSE);
g_task_set_source_tag (task, gtk_uri_launcher_launch);
if (self->uri == NULL)
{
g_task_return_new_error (task,
GTK_DIALOG_ERROR, GTK_DIALOG_ERROR_FAILED,
"No uri to launch");
return;
}
#ifndef G_OS_WIN32
if (g_openuri_portal_is_available ())
g_openuri_portal_open_uri_async (self->uri, parent, cancellable, open_done, task);
else
#endif
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
gtk_show_uri_full (parent, self->uri, GDK_CURRENT_TIME, cancellable, show_uri_done, task);
G_GNUC_END_IGNORE_DEPRECATIONS
}
/**
* gtk_uri_launcher_launch_finish:
* @self: a `GtkUriLauncher`
* @result: a `GAsyncResult`
* @error: return location for a [enum@Gtk.DialogError] or [enum@Gio.Error] error
*
* Finishes the [method@Gtk.UriLauncher.launch] call and
* returns the result.
*
* Returns: `TRUE` if an application was launched,
* or `FALSE` and @error is set
*
* Since: 4.10
*/
gboolean
gtk_uri_launcher_launch_finish (GtkUriLauncher *self,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (GTK_IS_URI_LAUNCHER (self), FALSE);
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
g_return_val_if_fail (g_task_get_source_tag (G_TASK (result)) == gtk_uri_launcher_launch, FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

57
gtk/gtkurilauncher.h Normal file
View File

@@ -0,0 +1,57 @@
/* GTK - The GIMP Toolkit
*
* Copyright (C) 2022 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/>.
*/
#pragma once
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gdk/gdk.h>
#include <gtk/gtkwindow.h>
G_BEGIN_DECLS
#define GTK_TYPE_URI_LAUNCHER (gtk_uri_launcher_get_type ())
GDK_AVAILABLE_IN_4_10
G_DECLARE_FINAL_TYPE (GtkUriLauncher, gtk_uri_launcher, GTK, URI_LAUNCHER, GObject)
GDK_AVAILABLE_IN_4_10
GtkUriLauncher * gtk_uri_launcher_new (const char *uri);
GDK_AVAILABLE_IN_4_10
const char * gtk_uri_launcher_get_uri (GtkUriLauncher *self);
GDK_AVAILABLE_IN_4_10
void gtk_uri_launcher_set_uri (GtkUriLauncher *self,
const char *uri);
GDK_AVAILABLE_IN_4_10
void gtk_uri_launcher_launch (GtkUriLauncher *self,
GtkWindow *parent,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
GDK_AVAILABLE_IN_4_10
gboolean gtk_uri_launcher_launch_finish (GtkUriLauncher *self,
GAsyncResult *result,
GError **error);
G_END_DECLS

View File

@@ -390,6 +390,7 @@ gtk_public_sources = files([
'gtktreeexpander.c',
'gtktreelistmodel.c',
'gtktreelistrowsorter.c',
'gtkurilauncher.c',
'gtkversion.c',
'gtkvideo.c',
'gtkviewport.c',
@@ -615,6 +616,7 @@ gtk_public_headers = files([
'gtktreelistmodel.h',
'gtktreelistrowsorter.h',
'gtktypes.h',
'gtkurilauncher.h',
'gtkvideo.h',
'gtkviewport.h',
'gtkvolumebutton.h',