From 873939e8222a778e8f9855944483cb1ee1cb984b Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Thu, 6 Feb 2020 15:04:31 +0100 Subject: [PATCH] mime-actions: Group files depending on the opening app Reinstate the old behaviour which used to look at the files to open, group them by handling application, and pass a single call to each application with all the files it could handle. For the common case where it's a single application handling all the files, as is usual for handling media files (images, videos, music, etc.), it changes the launching behaviour from: application.bin foo.mp3 application.bin foo2.mp3 application.bin foo3.mp3 to: application.bin foo.mp3 foo2.mp3 foo3.mp3 This however impacts the behaviour of nautilus launching applications when inside a Flatpak sandbox, as it cannot enumerate and launch applications itself. As the Flatpak sandbox is a development tool, and for the sake of expediency, we reverted to the old code path. Revert "mime-actions: launch default uri handlers when activating files" This reverts commit f5206a6daf0991d91e885a28bb66795a8ae12a41. Closes: #117 --- src/nautilus-mime-actions.c | 268 +++++++++++++++++++++++--------- src/nautilus-program-choosing.c | 188 +++------------------- src/nautilus-program-choosing.h | 7 - 3 files changed, 211 insertions(+), 252 deletions(-) diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c index 26468c597..4a49828a8 100644 --- a/src/nautilus-mime-actions.c +++ b/src/nautilus-mime-actions.c @@ -61,6 +61,12 @@ typedef struct char *uri; } LaunchLocation; +typedef struct +{ + GAppInfo *application; + GList *uris; +} ApplicationLaunchParameters; + typedef struct { NautilusWindowSlot *slot; @@ -80,13 +86,6 @@ typedef struct gboolean user_confirmation; } ActivateParameters; -typedef struct -{ - ActivateParameters *activation_params; - GQueue *uris; - GQueue *unhandled_uris; -} ApplicationLaunchParameters; - /* Microsoft mime types at https://blogs.msdn.microsoft.com/vsofficedeveloper/2008/05/08/office-2007-file-format-mime-types-for-http-content-streaming-2/ */ struct { @@ -345,19 +344,27 @@ launch_locations_from_file_list (GList *list) } static ApplicationLaunchParameters * -application_launch_parameters_new (ActivateParameters *activation_params, - GQueue *uris) +application_launch_parameters_new (GAppInfo *application, + GList *uris) { ApplicationLaunchParameters *result; result = g_new0 (ApplicationLaunchParameters, 1); - result->activation_params = activation_params; - result->uris = uris; - result->unhandled_uris = g_queue_new (); + result->application = g_object_ref (application); + result->uris = g_list_copy_deep (uris, (GCopyFunc) g_strdup, NULL); 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) { @@ -792,6 +799,114 @@ 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) { @@ -843,16 +958,6 @@ 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) { @@ -1369,55 +1474,22 @@ out: show_unhandled_type_error (parameters_install); } -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 != NULL && 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 - { - 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; 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; + g_autoptr (GQueue) launch_desktop_files = NULL; g_autoptr (GQueue) launch_files = NULL; g_autoptr (GQueue) launch_in_terminal_files = NULL; g_autoptr (GQueue) open_in_app_uris = NULL; @@ -1612,26 +1684,68 @@ activate_files (ActivateParameters *parameters) } } - if (g_queue_is_empty (open_in_app_uris)) + if (open_in_app_uris != NULL) { - activation_parameters_free (parameters); + open_in_app_parameters = make_activation_parameters (g_queue_peek_head_link (open_in_app_uris), + &unhandled_open_in_app_uris); } - else + + 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) { - const char *uri; - ApplicationLaunchParameters *params; + GtkDialog *dialog; + char *prompt; + g_autofree char *detail = NULL; + int response; - uri = g_queue_peek_head (open_in_app_uris); - params = application_launch_parameters_new (parameters, - g_queue_copy (open_in_app_uris)); + pause_activation_timed_cancel (parameters); - gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri); - nautilus_launch_default_for_uri_async (uri, - parameters->parent_window, - parameters->cancellable, - on_launch_default_for_uri, - params); + 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) + { + open_files = FALSE; + } + } + + 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); + } } + + activation_parameters_free (parameters); } static void diff --git a/src/nautilus-program-choosing.c b/src/nautilus-program-choosing.c index 47362a3f7..35a4ab73f 100644 --- a/src/nautilus-program-choosing.c +++ b/src/nautilus-program-choosing.c @@ -126,32 +126,6 @@ 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, @@ -163,7 +137,8 @@ nautilus_launch_application_by_uri (GAppInfo *application, NautilusFile *file; gboolean result; GError *error; - g_autoptr (GdkAppLaunchContext) launch_context = NULL; + GdkDisplay *display; + GdkAppLaunchContext *launch_context; NautilusIconInfo *icon; int count, total; @@ -186,7 +161,22 @@ nautilus_launch_application_by_uri (GAppInfo *application, } locations = g_list_reverse (locations); - launch_context = get_launch_context (parent_window); + 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)); + } file = nautilus_file_get_by_uri (uris->data); icon = nautilus_file_get_icon (file, @@ -222,6 +212,8 @@ nautilus_launch_application_by_uri (GAppInfo *application, &error); } + g_object_unref (launch_context); + if (result) { for (l = uris; l != NULL; l = l->next) @@ -480,144 +472,4 @@ nautilus_launch_desktop_file (GdkScreen *screen, 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. - */ - -static void -on_launch_default_for_uri (GObject *source, - GAsyncResult *result, - gpointer data) -{ - GTask *task; - NautilusWindow *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) - { - nautilus_window_unexport_handle (window); - } - - if (success) - { - g_task_return_boolean (task, success); - } - else - { - g_task_return_error (task, error); - } - - /* Reffed in the call to nautilus_window_export_handle */ - g_object_unref (task); -} - -static void -on_window_handle_export (NautilusWindow *window, - const char *handle_str, - guint xid, - 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 = nautilus_window_export_handle (NAUTILUS_WINDOW (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 51881ff17..a402b79a2 100644 --- a/src/nautilus-program-choosing.h +++ b/src/nautilus-program-choosing.h @@ -51,10 +51,3 @@ 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); \ No newline at end of file -- GitLab