Commit f5206a6d authored by Ernestas Kulik's avatar Ernestas Kulik 🦑

mime-actions: launch default uri handlers when activating files

Currently, Nautilus tries to find the default applications for files
itself, which does not work well in a sandbox. This commit makes
Nautilus blindly launch the default applications, which makes use of the
documents portal indirectly.

https://bugzilla.gnome.org/show_bug.cgi?id=781132
parent a4931f4a
......@@ -65,12 +65,6 @@ typedef struct
char *uri;
} LaunchLocation;
typedef struct
{
GAppInfo *application;
GList *uris;
} ApplicationLaunchParameters;
typedef struct
{
NautilusWindowSlot *slot;
......@@ -90,6 +84,13 @@ typedef struct
gboolean user_confirmation;
} ActivateParameters;
typedef struct
{
ActivateParameters *activation_params;
GQueue *uris;
GQueue *unhandled_uris;
} ApplicationLaunchParameters;
struct
{
char *name;
......@@ -343,27 +344,19 @@ launch_locations_from_file_list (GList *list)
}
static ApplicationLaunchParameters *
application_launch_parameters_new (GAppInfo *application,
GList *uris)
application_launch_parameters_new (ActivateParameters *activation_params,
GQueue *uris)
{
ApplicationLaunchParameters *result;
result = g_new0 (ApplicationLaunchParameters, 1);
result->application = g_object_ref (application);
result->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL);
result->activation_params = activation_params;
result->uris = uris;
result->unhandled_uris = g_queue_new ();
return result;
}
static void
application_launch_parameters_free (ApplicationLaunchParameters *parameters)
{
g_object_unref (parameters->application);
g_list_free_full (parameters->uris, g_free);
g_free (parameters);
}
static gboolean
nautilus_mime_actions_check_if_required_attributes_ready (NautilusFile *file)
{
......@@ -797,114 +790,6 @@ nautilus_mime_file_opens_in_external_app (NautilusFile *file)
return (activation_action == ACTIVATION_ACTION_OPEN_IN_APPLICATION);
}
static unsigned int
mime_application_hash (GAppInfo *app)
{
const char *id;
id = g_app_info_get_id (app);
if (id == NULL)
{
return GPOINTER_TO_UINT (app);
}
return g_str_hash (id);
}
static void
list_to_parameters_foreach (GAppInfo *application,
GList *uris,
GList **ret)
{
ApplicationLaunchParameters *parameters;
uris = g_list_reverse (uris);
parameters = application_launch_parameters_new
(application, uris);
*ret = g_list_prepend (*ret, parameters);
}
/**
* make_activation_parameters
*
* Construct a list of ApplicationLaunchParameters from a list of NautilusFiles,
* where files that have the same default application are put into the same
* launch parameter, and others are put into the unhandled_files list.
*
* @files: Files to use for construction.
* @unhandled_files: Files without any default application will be put here.
*
* Return value: Newly allocated list of ApplicationLaunchParameters.
**/
static GList *
make_activation_parameters (GList *uris,
GList **unhandled_uris)
{
GList *ret, *l, *app_uris;
NautilusFile *file;
GAppInfo *app, *old_app;
GHashTable *app_table;
char *uri;
ret = NULL;
*unhandled_uris = NULL;
app_table = g_hash_table_new_full
((GHashFunc) mime_application_hash,
(GEqualFunc) g_app_info_equal,
(GDestroyNotify) g_object_unref,
(GDestroyNotify) g_list_free);
for (l = uris; l != NULL; l = l->next)
{
uri = l->data;
file = nautilus_file_get_by_uri (uri);
app = nautilus_mime_get_default_application_for_file (file);
if (app != NULL)
{
app_uris = NULL;
if (g_hash_table_lookup_extended (app_table, app,
(gpointer *) &old_app,
(gpointer *) &app_uris))
{
g_hash_table_steal (app_table, old_app);
app_uris = g_list_prepend (app_uris, uri);
g_object_unref (app);
app = old_app;
}
else
{
app_uris = g_list_prepend (NULL, uri);
}
g_hash_table_insert (app_table, app, app_uris);
}
else
{
*unhandled_uris = g_list_prepend (*unhandled_uris, uri);
}
nautilus_file_unref (file);
}
g_hash_table_foreach (app_table,
(GHFunc) list_to_parameters_foreach,
&ret);
g_hash_table_destroy (app_table);
*unhandled_uris = g_list_reverse (*unhandled_uris);
return g_list_reverse (ret);
}
static gboolean
file_was_cancelled (NautilusFile *file)
{
......@@ -956,6 +841,16 @@ activation_parameters_free (ActivateParameters *parameters)
g_free (parameters);
}
static void
application_launch_parameters_free (ApplicationLaunchParameters *parameters)
{
g_queue_free (parameters->unhandled_uris);
g_queue_free (parameters->uris);
activation_parameters_free (parameters->activation_params);
g_free (parameters);
}
static void
cancel_activate_callback (gpointer callback_data)
{
......@@ -1628,22 +1523,70 @@ activate_desktop_file (ActivateParameters *parameters,
g_free (uri);
}
static void
on_launch_default_for_uri (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
ApplicationLaunchParameters *params;
ActivateParameters *activation_params;
char *uri;
gboolean sandboxed;
GError *error = NULL;
params = user_data;
activation_params = params->activation_params;
uri = g_queue_pop_head (params->uris);
sandboxed = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS);
nautilus_launch_default_for_uri_finish (res, &error);
if (!sandboxed && error->code != G_IO_ERROR_CANCELLED)
{
g_queue_push_tail (params->unhandled_uris, uri);
}
if (!g_queue_is_empty (params->uris))
{
nautilus_launch_default_for_uri_async (g_queue_peek_head (params->uris),
activation_params->parent_window,
activation_params->cancellable,
on_launch_default_for_uri,
params);
}
else
{
gboolean should_close;
NautilusWindow *window;
should_close = activation_params->flags &
NAUTILUS_WINDOW_OPEN_FLAG_CLOSE_BEHIND;
window = nautilus_window_slot_get_window (activation_params->slot);
if (should_close && window != NULL)
{
nautilus_window_close (window);
}
else
{
while ((uri = g_queue_pop_head (params->unhandled_uris)) != NULL)
{
application_unhandled_uri (activation_params, uri);
}
}
application_launch_parameters_free (params);
}
}
static void
activate_files (ActivateParameters *parameters)
{
NautilusFile *file;
NautilusWindow *window;
NautilusWindowOpenFlags flags;
g_autoptr (GList) open_in_app_parameters = NULL;
g_autoptr (GList) unhandled_open_in_app_uris = NULL;
ApplicationLaunchParameters *one_parameters;
int count;
g_autofree char *old_working_dir = NULL;
GdkScreen *screen;
gint num_apps;
gint num_unhandled;
gint num_files;
gboolean open_files;
gboolean closed_window;
g_autoptr (GQueue) launch_desktop_files = NULL;
g_autoptr (GQueue) launch_files = NULL;
......@@ -1890,84 +1833,37 @@ activate_files (ActivateParameters *parameters)
}
}
if (open_in_app_uris != NULL)
if (g_queue_is_empty (open_in_app_uris))
{
open_in_app_parameters = make_activation_parameters (g_queue_peek_head_link (open_in_app_uris),
&unhandled_open_in_app_uris);
}
num_apps = g_list_length (open_in_app_parameters);
num_unhandled = g_list_length (unhandled_open_in_app_uris);
num_files = g_queue_get_length (open_in_app_uris);
open_files = TRUE;
if (g_queue_is_empty (open_in_app_uris) &&
(!parameters->user_confirmation ||
num_files + num_unhandled > SILENT_OPEN_LIMIT) &&
num_apps > 1)
{
GtkDialog *dialog;
char *prompt;
g_autofree char *detail = NULL;
int response;
pause_activation_timed_cancel (parameters);
prompt = _("Are you sure you want to open all files?");
detail = g_strdup_printf (ngettext ("This will open %d separate application.",
"This will open %d separate applications.", num_apps), num_apps);
dialog = eel_show_yes_no_dialog (prompt, detail,
_("_OK"), _("_Cancel"),
parameters->parent_window);
response = gtk_dialog_run (dialog);
gtk_widget_destroy (GTK_WIDGET (dialog));
unpause_activation_timed_cancel (parameters);
if (response != GTK_RESPONSE_YES)
window = NULL;
if (parameters->slot != NULL)
{
open_files = FALSE;
window = nautilus_window_slot_get_window (parameters->slot);
}
}
if (open_files)
{
for (l = open_in_app_parameters; l != NULL; l = l->next)
{
one_parameters = l->data;
nautilus_launch_application_by_uri (one_parameters->application,
one_parameters->uris,
parameters->parent_window);
application_launch_parameters_free (one_parameters);
}
for (l = unhandled_open_in_app_uris; l != NULL; l = l->next)
{
char *uri = l->data;
/* this does not block */
application_unhandled_uri (parameters, uri);
}
}
window = NULL;
if (parameters->slot != NULL)
{
window = nautilus_window_slot_get_window (parameters->slot);
}
if (open_in_app_parameters != NULL ||
unhandled_open_in_app_uris != NULL)
{
if ((parameters->flags & NAUTILUS_WINDOW_OPEN_FLAG_CLOSE_BEHIND) != 0 &&
window != NULL)
{
nautilus_window_close (window);
}
activation_parameters_free (parameters);
}
else
{
const char *uri;
ApplicationLaunchParameters *params;
activation_parameters_free (parameters);
uri = g_queue_peek_head (open_in_app_uris);
params = application_launch_parameters_new (parameters,
g_queue_copy (open_in_app_uris));
nautilus_launch_default_for_uri_async (uri,
parameters->parent_window,
parameters->cancellable,
on_launch_default_for_uri,
params);
}
}
static void
......
......@@ -86,6 +86,32 @@ nautilus_launch_application (GAppInfo *application,
g_list_free_full (uris, g_free);
}
static GdkAppLaunchContext *
get_launch_context (GtkWindow *parent_window)
{
GdkDisplay *display;
GdkAppLaunchContext *launch_context;
if (parent_window != NULL)
{
display = gtk_widget_get_display (GTK_WIDGET (parent_window));
}
else
{
display = gdk_display_get_default ();
}
launch_context = gdk_display_get_app_launch_context (display);
if (parent_window != NULL)
{
gdk_app_launch_context_set_screen (launch_context,
gtk_window_get_screen (parent_window));
}
return launch_context;
}
void
nautilus_launch_application_by_uri (GAppInfo *application,
GList *uris,
......@@ -97,8 +123,7 @@ nautilus_launch_application_by_uri (GAppInfo *application,
NautilusFile *file;
gboolean result;
GError *error;
GdkDisplay *display;
GdkAppLaunchContext *launch_context;
g_autoptr (GdkAppLaunchContext) launch_context = NULL;
NautilusIconInfo *icon;
int count, total;
......@@ -121,22 +146,7 @@ nautilus_launch_application_by_uri (GAppInfo *application,
}
locations = g_list_reverse (locations);
if (parent_window != NULL)
{
display = gtk_widget_get_display (GTK_WIDGET (parent_window));
}
else
{
display = gdk_display_get_default ();
}
launch_context = gdk_display_get_app_launch_context (display);
if (parent_window != NULL)
{
gdk_app_launch_context_set_screen (launch_context,
gtk_window_get_screen (parent_window));
}
launch_context = get_launch_context (parent_window);
file = nautilus_file_get_by_uri (uris->data);
icon = nautilus_file_get_icon (file,
......@@ -172,8 +182,6 @@ nautilus_launch_application_by_uri (GAppInfo *application,
&error);
}
g_object_unref (launch_context);
if (result)
{
for (l = uris; l != NULL; l = l->next)
......@@ -430,3 +438,239 @@ nautilus_launch_desktop_file (GdkScreen *screen,
g_object_unref (context);
g_object_unref (app_info);
}
/* HAX
*
* TODO: remove everything below once it’s doable from GTK+.
*
* Context: https://bugzilla.gnome.org/show_bug.cgi?id=781132 and
* https://bugzilla.gnome.org/show_bug.cgi?id=779312
*
* In a sandboxed environment, this is needed to able to get the actual
* result of the operation, since gtk_show_uri_on_window () neither blocks
* nor returns a useful value.
*/
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/gdkwayland.h>
#endif
typedef void (*GtkWindowHandleExported) (GtkWindow *window,
const char *handle,
gpointer user_data);
#ifdef GDK_WINDOWING_WAYLAND
typedef struct
{
GtkWindow *window;
GtkWindowHandleExported callback;
gpointer user_data;
} WaylandWindowHandleExportedData;
static void
wayland_window_handle_exported (GdkWindow *window,
const char *wayland_handle_str,
gpointer user_data)
{
WaylandWindowHandleExportedData *data = user_data;
char *handle_str;
handle_str = g_strdup_printf ("wayland:%s", wayland_handle_str);
data->callback (data->window, handle_str, data->user_data);
g_free (handle_str);
g_free (data);
}
#endif
static gboolean
window_export_handle (GtkWindow *window,
GtkWindowHandleExported callback,
gpointer user_data)
{
#ifdef GDK_WINDOWING_X11
if (GDK_IS_X11_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
{
GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
char *handle_str;
guint32 xid = (guint32) gdk_x11_window_get_xid (gdk_window);
handle_str = g_strdup_printf ("x11:%x", xid);
callback (window, handle_str, user_data);
return TRUE;
}
#endif
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
{
GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
WaylandWindowHandleExportedData *data;
data = g_new0 (WaylandWindowHandleExportedData, 1);
data->window = window;
data->callback = callback;
data->user_data = user_data;
if (!gdk_wayland_window_export_handle (gdk_window,
wayland_window_handle_exported,
data,
g_free))
{
g_free (data);
return FALSE;
}
else
{
return TRUE;
}
}
#endif
g_warning ("Couldn't export handle, unsupported windowing system");
return FALSE;
}
void
gtk_window_unexport_handle (GtkWindow *window)
{
#ifdef GDK_WINDOWING_WAYLAND
if (GDK_IS_WAYLAND_DISPLAY (gtk_widget_get_display (GTK_WIDGET (window))))
{
GdkWindow *gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
gdk_wayland_window_unexport_handle (gdk_window);
}
#endif
}
static void
on_launch_default_for_uri (GObject *source,
GAsyncResult *result,
gpointer data)
{
GTask *task;
GtkWindow *window;
gboolean success;
GError *error = NULL;
task = data;
window = g_task_get_source_object (task);
success = g_app_info_launch_default_for_uri_finish (result, &error);
if (window)
{
gtk_window_unexport_handle (window);
}
if (success)
{
g_task_return_boolean (task, success);
}
else
{
g_task_return_error (task, error);
}
/* Reffed in the call to window_export_handle */
g_object_unref (task);
}
static void
on_window_handle_export (GtkWindow *window,
const char *handle_str,
gpointer user_data)
{
GTask *task = user_data;
GAppLaunchContext *context = g_task_get_task_data (task);
const char *uri;
uri = g_object_get_data (G_OBJECT (context), "uri");
g_app_launch_context_setenv (context, "PARENT_WINDOW_ID", handle_str);
g_app_info_launch_default_for_uri_async (uri,
context,
g_task_get_cancellable (task),
on_launch_default_for_uri,
task);
}
static void
launch_default_for_uri_thread_func (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GAppLaunchContext *launch_context;
const char *uri;
gboolean success;
GError *error = NULL;
launch_context = task_data;
uri = g_object_get_data (G_OBJECT (launch_context), "uri");
success = g_app_info_launch_default_for_uri (uri, launch_context, &error);
if (success)
{
g_task_return_boolean (task, success);
}
else
{
g_task_return_error (task, error);
}
}
void
nautilus_launch_default_for_uri_async (const char *uri,
GtkWindow *parent_window,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer callback_data)
{
g_autoptr (GdkAppLaunchContext) launch_context = NULL;
g_autoptr (GTask) task = NULL;
g_return_if_fail (uri != NULL);
launch_context = get_launch_context (parent_window);