From: =?utf-8?q?Ant=C3=B3nio_Fernandes?= Date: Wed, 30 Dec 2020 15:54:59 +0000 Subject: mime-actions: Open files as groups if not sandboxed While sandboxed, we open files using the OpenURI portal, and we don't know which app is the default handler app for each file. As such, we have given up group-launching files with the same default handler when adapting nautilus to being sandboxed.[0] But this resulted in a feature regression in the non-sandboxed case, which is still the common case in production. Reinstate the code for the old behaviour[1], but keep the current behaviour when running in inside a flatpak sandbox. Bug: https://gitlab.gnome.org/GNOME/nautilus/-/issues/117 [0] f5206a6daf0991d91e885a28bb66795a8ae12a41 [1] based on revert patch with revert conflicts resolved by hadess Origin: upstream, 40.rc, commit:080f83385ff79a8be54ee31e7a45422138226f1f --- src/nautilus-mime-actions.c | 238 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 215 insertions(+), 23 deletions(-) diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c index 26468c5..cbff2fa 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; @@ -85,7 +91,7 @@ typedef struct ActivateParameters *activation_params; GQueue *uris; GQueue *unhandled_uris; -} ApplicationLaunchParameters; +} ApplicationLaunchAsyncParameters; /* 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 +351,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 +806,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) { @@ -844,7 +966,7 @@ activation_parameters_free (ActivateParameters *parameters) } static void -application_launch_parameters_free (ApplicationLaunchParameters *parameters) +application_launch_async_parameters_free (ApplicationLaunchAsyncParameters *parameters) { g_queue_free (parameters->unhandled_uris); g_queue_free (parameters->uris); @@ -1370,11 +1492,11 @@ out: } static void -on_launch_default_for_uri (GObject *source_object, - GAsyncResult *res, - gpointer user_data) +launch_default_for_uris_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) { - ApplicationLaunchParameters *params; + ApplicationLaunchAsyncParameters *params; ActivateParameters *activation_params; char *uri; gboolean sandboxed; @@ -1396,7 +1518,7 @@ on_launch_default_for_uri (GObject *source_object, nautilus_launch_default_for_uri_async (g_queue_peek_head (params->uris), activation_params->parent_window, activation_params->cancellable, - on_launch_default_for_uri, + launch_default_for_uris_callback, params); } else @@ -1406,7 +1528,7 @@ on_launch_default_for_uri (GObject *source_object, application_unhandled_uri (activation_params, uri); } - application_launch_parameters_free (params); + application_launch_async_parameters_free (params); } } @@ -1415,9 +1537,16 @@ 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_files = NULL; g_autoptr (GQueue) launch_in_terminal_files = NULL; g_autoptr (GQueue) open_in_app_uris = NULL; @@ -1612,26 +1741,89 @@ activate_files (ActivateParameters *parameters) } } - if (g_queue_is_empty (open_in_app_uris)) - { - activation_parameters_free (parameters); - } - else + if (!g_queue_is_empty (open_in_app_uris) && + g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS)) { const char *uri; - ApplicationLaunchParameters *params; + ApplicationLaunchAsyncParameters *async_params; uri = g_queue_peek_head (open_in_app_uris); - params = application_launch_parameters_new (parameters, - g_queue_copy (open_in_app_uris)); + + async_params = g_new0 (ApplicationLaunchAsyncParameters, 1); + async_params->activation_params = parameters; + async_params->uris = g_steal_pointer (&open_in_app_uris); 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); + launch_default_for_uris_callback, + async_params); + return; + } + + if (open_in_app_uris != NULL) + { + 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) + { + 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