From 2d848bba7bd24f9d29f372c1e37507aacfbedda5 Mon Sep 17 00:00:00 2001 From: Ernestas Kulik Date: Sun, 9 Apr 2017 10:01:22 +0300 Subject: 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 --- src/nautilus-mime-actions.c | 294 +++++++++++++--------------------------- src/nautilus-program-choosing.c | 277 ++++++++++++++++++++++++++++++++++--- src/nautilus-program-choosing.h | 7 + 3 files changed, 357 insertions(+), 221 deletions(-) diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c index 14fe44bc3..f4a25063b 100644 --- a/src/nautilus-mime-actions.c +++ b/src/nautilus-mime-actions.c @@ -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,66 @@ 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; + + params = user_data; + activation_params = params->activation_params; + uri = g_queue_pop_head (params->uris); + + if (!nautilus_launch_default_for_uri_finish (res, NULL)) + { + 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 +1829,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 diff --git a/src/nautilus-program-choosing.c b/src/nautilus-program-choosing.c index 92c6ddcf7..bb3c5c393 100644 --- a/src/nautilus-program-choosing.c +++ b/src/nautilus-program-choosing.c @@ -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,232 @@ 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+. + */ + +#ifdef GDK_WINDOWING_WAYLAND +#include +#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); + task = g_task_new (parent_window, cancellable, callback, callback_data); + + gdk_app_launch_context_set_timestamp (launch_context, GDK_CURRENT_TIME); + + g_object_set_data_full (G_OBJECT (launch_context), + "uri", g_strdup (uri), g_free); + g_task_set_task_data (task, + g_object_ref (launch_context), g_object_unref); + + if (parent_window != NULL) + { + gboolean handle_exported; + + handle_exported = window_export_handle (parent_window, + on_window_handle_export, + g_object_ref (task)); + + if (handle_exported) + { + /* Launching will now be handled from the callback */ + return; + } + } + + g_task_run_in_thread (task, launch_default_for_uri_thread_func); +} + +gboolean +nautilus_launch_default_for_uri_finish (GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE); + + return g_task_propagate_boolean (G_TASK (result), error); +} + +/* END OF HAX */ diff --git a/src/nautilus-program-choosing.h b/src/nautilus-program-choosing.h index b92fc98fd..ded108aba 100644 --- a/src/nautilus-program-choosing.h +++ b/src/nautilus-program-choosing.h @@ -52,5 +52,12 @@ void nautilus_launch_desktop_file (GdkScreen const char *desktop_file_uri, const GList *parameter_uris, GtkWindow *parent_window); +void nautilus_launch_default_for_uri_async (const char *uri, + GtkWindow *parent_window, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer callback_data); +gboolean nautilus_launch_default_for_uri_finish (GAsyncResult *result, + GError **error); #endif /* NAUTILUS_PROGRAM_CHOOSING_H */ -- cgit v1.2.1