Package: nautilus Version: 3.38.2-1 Tags: patch Severity: important Justification: breaks common usage patterns and standards compliance
Dear maintainers, I've found that Nautilus always opens each selected file in a separate application instance even though the latter's desktop file Exec record contains %F or %U. This leads to the following issues. 1. This behavior breaks a common usage pattern when user selects multiple files and wants to open them in a single application. For example: * User wants to watch selected video files. With Nautilus 3.38, only one of them ends up in the play queue of GNOME's default video player (Totem) because opening each application instance overwrites the queue. It is even worse when using a player which allows simultanious instances leading to multiple videos playing at once. Same with audio files. * User wants to view selected images. With current behavior, each image opens in a separate Eye of GNOME window instead of opening the first one in a single window and cycling through the other ones. 2. Current behavior doesn't meet the Freedesktop.org desktop files standard[1]. 3. As explained in the upstream bug report[2], this annoying behavior was introduced for development testing purposes only: "This was introduced to ensure compatibility with Flatpak. At the time there was no API to allow grouping files by their type or somesuch and opening them all at once." "It has nothing to do with how many users use "flatpaks". It's about using nautilus in a Flatpak. Which only developers do as nautilus is not meant to be used as a sandboxed application, but as a system application." So this change was introduced for development testing purposes only, and Nautilus is not and never meant to be used sandboxed by end user. At the same time, this change breaks common usage patterns for people who are not Nautilus developers i.e. an overwhelming majority of users. 4. This problematic change was reverted in a subsequent MR[3] which unfortunately didn't make it into version 3.38. Given all above, could you consider backporting the fix into Nautilus in Debian 11 (stable), please? The patch in question, available for download from the aforementioned MR, applies cleanly to the version in stable and works flawlessly, providing both the correct behavior for end users *and* Flatpak support. The patch is also attached to this message for your convenience. [1] https://specifications.freedesktop.org/desktop-entry-spec/latest/ar01s07.html [2] https://gitlab.gnome.org/GNOME/nautilus/-/issues/117 [3] https://gitlab.gnome.org/GNOME/nautilus/-/merge_requests/518 -- Regards, Алексей Шилин
diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c index 1a3abff387b19c82e37d2c4179e3a62e033f3438..ecccd8efcddc93d7b1169fab0fee5d1d6a154446 100644 --- a/src/nautilus-mime-actions.c +++ b/src/nautilus-mime-actions.c @@ -60,6 +60,12 @@ typedef struct char *uri; } LaunchLocation; +typedef struct +{ + GAppInfo *application; + GList *uris; +} ApplicationLaunchParameters; + typedef struct { NautilusWindowSlot *slot; @@ -83,8 +89,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 @@ -239,6 +244,20 @@ static void activate_callback (GList *files, gpointer callback_data); static void activation_mount_not_mounted (ActivateParameters *parameters); +static gboolean +is_sandboxed (void) +{ + static gboolean ret; + + static gsize init = 0; + if (g_once_init_enter (&init)) + { + ret = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); + g_once_init_leave (&init, 1); + } + + return ret; +} static void launch_location_free (LaunchLocation *location) @@ -340,19 +359,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) { @@ -684,6 +711,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) { @@ -736,9 +871,8 @@ 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); activation_parameters_free (parameters->activation_params); @@ -1262,25 +1396,23 @@ 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; - GError *error = NULL; + g_autoptr (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) + if (error == NULL) { - g_queue_push_tail (params->unhandled_uris, uri); + gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri); } if (!g_queue_is_empty (params->uris)) @@ -1288,17 +1420,12 @@ 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 { - while ((uri = g_queue_pop_head (params->unhandled_uris)) != NULL) - { - application_unhandled_uri (activation_params, uri); - } - - application_launch_parameters_free (params); + application_launch_async_parameters_free (params); } } @@ -1307,9 +1434,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; @@ -1489,26 +1623,87 @@ 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) && is_sandboxed ()) { 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)); - gtk_recent_manager_add_item (gtk_recent_manager_get_default (), uri); + async_params = g_new0 (ApplicationLaunchAsyncParameters, 1); + async_params->activation_params = parameters; + async_params->uris = g_steal_pointer (&open_in_app_uris); + 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