diff options
Diffstat (limited to '')
-rw-r--r-- | src/nautilus-mime-actions.c | 2325 |
1 files changed, 2325 insertions, 0 deletions
diff --git a/src/nautilus-mime-actions.c b/src/nautilus-mime-actions.c new file mode 100644 index 0000000..d4dfd16 --- /dev/null +++ b/src/nautilus-mime-actions.c @@ -0,0 +1,2325 @@ +/* nautilus-mime-actions.c - uri-specific versions of mime action functions + * + * Copyright (C) 2000, 2001 Eazel, Inc. + * + * The Gnome Library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * The Gnome Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with the Gnome Library; see the file COPYING.LIB. If not, + * see <http://www.gnu.org/licenses/>. + * + * Authors: Maciej Stachowiak <mjs@eazel.com> + */ + +#include "nautilus-mime-actions.h" + +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <string.h> + +#define DEBUG_FLAG NAUTILUS_DEBUG_MIME +#include "nautilus-debug.h" + +#include "nautilus-application.h" +#include "nautilus-enums.h" +#include "nautilus-file.h" +#include "nautilus-file-utilities.h" +#include "nautilus-file-operations.h" +#include "nautilus-global-preferences.h" +#include "nautilus-metadata.h" +#include "nautilus-program-choosing.h" +#include "nautilus-signaller.h" +#include "nautilus-ui-utilities.h" +#include "nautilus-window.h" +#include "nautilus-window-slot.h" + +typedef enum +{ + ACTIVATION_ACTION_LAUNCH, + ACTIVATION_ACTION_LAUNCH_IN_TERMINAL, + ACTIVATION_ACTION_OPEN_IN_VIEW, + ACTIVATION_ACTION_OPEN_IN_APPLICATION, + ACTIVATION_ACTION_EXTRACT, + ACTIVATION_ACTION_DO_NOTHING, +} ActivationAction; + +typedef struct +{ + NautilusFile *file; + char *uri; +} LaunchLocation; + +typedef struct +{ + GAppInfo *application; + GList *uris; +} ApplicationLaunchParameters; + +typedef struct +{ + NautilusWindowSlot *slot; + gpointer window; + GtkWindow *parent_window; + GCancellable *cancellable; + GList *locations; + GList *mountables; + GList *start_mountables; + GList *not_mounted; + NautilusOpenFlags flags; + char *timed_wait_prompt; + gboolean timed_wait_active; + NautilusFileListHandle *files_handle; + gboolean tried_mounting; + char *activation_directory; + gboolean user_confirmation; + GQueue *open_in_view_files; + GQueue *open_in_app_uris; + GQueue *launch_files; + GQueue *launch_in_terminal_files; + GList *open_in_app_parameters; + GList *unhandled_open_in_app_uris; +} ActivateParameters; + +typedef struct +{ + ActivateParameters *activation_params; + GQueue *uris; +} 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 +{ + char *name; + char *mimetypes[20]; +} mimetype_groups[] = +{ + { + N_("Anything"), + { NULL } + }, + { + N_("Files"), + { "application/octet-stream", + "text/plain", + NULL} + }, + { + N_("Folders"), + { "inode/directory", + NULL} + }, + { N_("Documents"), + { "application/rtf", + "application/msword", + "application/vnd.sun.xml.writer", + "application/vnd.sun.xml.writer.global", + "application/vnd.sun.xml.writer.template", + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.text-template", + "application/x-abiword", + "application/x-applix-word", + "application/x-mswrite", + "application/docbook+xml", + "application/x-kword", + "application/x-kword-crypt", + "application/x-lyx", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + NULL}}, + { N_("Illustration"), + { "application/illustrator", + "application/vnd.corel-draw", + "application/vnd.stardivision.draw", + "application/vnd.oasis.opendocument.graphics", + "application/x-dia-diagram", + "application/x-karbon", + "application/x-killustrator", + "application/x-kivio", + "application/x-kontour", + "application/x-wpg", + NULL}}, + { N_("Music"), + { "application/ogg", + "audio/x-vorbis+ogg", + "audio/ac3", + "audio/basic", + "audio/midi", + "audio/x-flac", + "audio/mp4", + "audio/mpeg", + "audio/x-mpeg", + "audio/x-ms-asx", + "audio/x-pn-realaudio", + NULL}}, + { N_("PDF / PostScript"), + { "application/pdf", + "application/postscript", + "application/x-dvi", + "image/x-eps", + "image/vnd.djvu+multipage", + NULL}}, + { N_("Picture"), + { "application/vnd.oasis.opendocument.image", + "application/x-krita", + "image/bmp", + "image/cgm", + "image/gif", + "image/jpeg", + "image/jpeg2000", + "image/png", + "image/svg+xml", + "image/tiff", + "image/x-compressed-xcf", + "image/x-pcx", + "image/x-photo-cd", + "image/x-psd", + "image/x-tga", + "image/x-xcf", + NULL}}, + { N_("Presentation"), + { "application/vnd.ms-powerpoint", + "application/vnd.sun.xml.impress", + "application/vnd.oasis.opendocument.presentation", + "application/x-magicpoint", + "application/x-kpresenter", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + NULL}}, + { N_("Spreadsheet"), + { "application/vnd.lotus-1-2-3", + "application/vnd.ms-excel", + "application/vnd.stardivision.calc", + "application/vnd.sun.xml.calc", + "application/vnd.oasis.opendocument.spreadsheet", + "application/x-applix-spreadsheet", + "application/x-gnumeric", + "application/x-kspread", + "application/x-kspread-crypt", + "application/x-quattropro", + "application/x-sc", + "application/x-siag", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + NULL}}, + { N_("Text File"), + { "text/plain", + NULL}}, + { N_("Video"), + { "video/mp4", + "video/3gpp", + "video/mpeg", + "video/quicktime", + "video/vivo", + "video/x-avi", + "video/x-mng", + "video/x-ms-asf", + "video/x-ms-wmv", + "video/x-msvideo", + "video/x-nsv", + "video/x-real-video", + NULL}} +}; + +/* Number of seconds until cancel dialog shows up */ +#define DELAY_UNTIL_CANCEL_MSECS 5000 + +#define SILENT_WINDOW_OPEN_LIMIT 5 +#define SILENT_OPEN_LIMIT 5 + +/* This number controls a maximum character count for a URL that is + * displayed as part of a dialog. It's fairly arbitrary -- big enough + * to allow most "normal" URIs to display in full, but small enough to + * prevent the dialog from getting insanely wide. + */ +#define MAX_URI_IN_DIALOG_LENGTH 60 + +static void activate_files_internal (ActivateParameters *parameters); +static void cancel_activate_callback (gpointer callback_data); +static void activate_activation_uris_ready_callback (GList *files, + gpointer callback_data); +static void activation_mount_mountables (ActivateParameters *parameters); +static void activation_start_mountables (ActivateParameters *parameters); +static void activate_callback (GList *files, + gpointer callback_data); +static void activation_mount_not_mounted (ActivateParameters *parameters); + +static void +launch_location_free (LaunchLocation *location) +{ + nautilus_file_unref (location->file); + g_free (location->uri); + g_free (location); +} + +static void +launch_location_list_free (GList *list) +{ + g_list_foreach (list, (GFunc) launch_location_free, NULL); + g_list_free (list); +} + +static GList * +get_file_list_for_launch_locations (GList *locations) +{ + GList *files, *l; + LaunchLocation *location; + + files = NULL; + for (l = locations; l != NULL; l = l->next) + { + location = l->data; + + files = g_list_prepend (files, + nautilus_file_ref (location->file)); + } + return g_list_reverse (files); +} + + +static LaunchLocation * +launch_location_from_file (NautilusFile *file) +{ + LaunchLocation *location; + location = g_new (LaunchLocation, 1); + location->file = nautilus_file_ref (file); + location->uri = nautilus_file_get_uri (file); + + return location; +} + +static void +launch_location_update_from_file (LaunchLocation *location, + NautilusFile *file) +{ + nautilus_file_unref (location->file); + g_free (location->uri); + location->file = nautilus_file_ref (file); + location->uri = nautilus_file_get_uri (file); +} + +static void +launch_location_update_from_uri (LaunchLocation *location, + const char *uri) +{ + nautilus_file_unref (location->file); + g_free (location->uri); + location->file = nautilus_file_get_by_uri (uri); + location->uri = g_strdup (uri); +} + +static LaunchLocation * +find_launch_location_for_file (GList *list, + NautilusFile *file) +{ + LaunchLocation *location; + GList *l; + + for (l = list; l != NULL; l = l->next) + { + location = l->data; + + if (location->file == file) + { + return location; + } + } + return NULL; +} + +static GList * +launch_locations_from_file_list (GList *list) +{ + GList *new; + + new = NULL; + while (list) + { + new = g_list_prepend (new, + launch_location_from_file (list->data)); + list = list->next; + } + new = g_list_reverse (new); + return new; +} + +static ApplicationLaunchParameters * +application_launch_parameters_new (GAppInfo *application, + GList *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); + + 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) +{ + NautilusFileAttributes attributes; + gboolean ready; + + attributes = nautilus_mime_actions_get_required_file_attributes (); + ready = nautilus_file_check_if_ready (file, attributes); + + return ready; +} + +NautilusFileAttributes +nautilus_mime_actions_get_required_file_attributes (void) +{ + return NAUTILUS_FILE_ATTRIBUTE_INFO; +} + +GAppInfo * +nautilus_mime_get_default_application_for_file (NautilusFile *file) +{ + GAppInfo *app; + char *mime_type; + char *uri_scheme; + + if (!nautilus_mime_actions_check_if_required_attributes_ready (file)) + { + return NULL; + } + + mime_type = nautilus_file_get_mime_type (file); + app = g_app_info_get_default_for_type (mime_type, + !nautilus_file_has_local_path (file)); + g_free (mime_type); + + if (app == NULL) + { + uri_scheme = nautilus_file_get_uri_scheme (file); + if (uri_scheme != NULL) + { + app = g_app_info_get_default_for_uri_scheme (uri_scheme); + g_free (uri_scheme); + } + } + + return app; +} + +static int +file_compare_by_mime_type (NautilusFile *file_a, + NautilusFile *file_b) +{ + char *mime_type_a, *mime_type_b; + int ret; + + mime_type_a = nautilus_file_get_mime_type (file_a); + mime_type_b = nautilus_file_get_mime_type (file_b); + + ret = strcmp (mime_type_a, mime_type_b); + + g_free (mime_type_a); + g_free (mime_type_b); + + return ret; +} + +static int +file_compare_by_parent_uri (NautilusFile *file_a, + NautilusFile *file_b) +{ + char *parent_uri_a, *parent_uri_b; + int ret; + + parent_uri_a = nautilus_file_get_parent_uri (file_a); + parent_uri_b = nautilus_file_get_parent_uri (file_b); + + ret = strcmp (parent_uri_a, parent_uri_b); + + g_free (parent_uri_a); + g_free (parent_uri_b); + + return ret; +} + +GAppInfo * +nautilus_mime_get_default_application_for_files (GList *files) +{ + GList *l, *sorted_files; + NautilusFile *file; + GAppInfo *app, *one_app; + + g_assert (files != NULL); + + sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type); + + app = NULL; + for (l = sorted_files; l != NULL; l = l->next) + { + file = l->data; + + if (l->prev && + file_compare_by_mime_type (file, l->prev->data) == 0 && + file_compare_by_parent_uri (file, l->prev->data) == 0) + { + continue; + } + + one_app = nautilus_mime_get_default_application_for_file (file); + if (one_app == NULL || (app != NULL && !g_app_info_equal (app, one_app))) + { + if (app) + { + g_object_unref (app); + } + if (one_app) + { + g_object_unref (one_app); + } + app = NULL; + break; + } + + if (app == NULL) + { + app = one_app; + } + else + { + g_object_unref (one_app); + } + } + + g_list_free (sorted_files); + + return app; +} + +static void +trash_or_delete_files (GtkWindow *parent_window, + const GList *files, + gboolean delete_if_all_already_in_trash) +{ + GList *locations; + const GList *node; + + locations = NULL; + for (node = files; node != NULL; node = node->next) + { + locations = g_list_prepend (locations, + nautilus_file_get_location ((NautilusFile *) node->data)); + } + + locations = g_list_reverse (locations); + + nautilus_file_operations_trash_or_delete_async (locations, + parent_window, + NULL, + NULL, NULL); + g_list_free_full (locations, g_object_unref); +} + +typedef struct +{ + GtkWindow *parent_window; + NautilusFile *file; +} TrashBrokenSymbolicLinkData; + +static void +trash_symbolic_link_cb (GtkDialog *dialog, + char *response, + gpointer user_data) +{ + g_autofree TrashBrokenSymbolicLinkData *data = NULL; + GList file_as_list; + + data = user_data; + + if (g_strcmp0 (response, "move-to-trash") == 0) + { + file_as_list.data = data->file; + file_as_list.next = NULL; + file_as_list.prev = NULL; + trash_or_delete_files (data->parent_window, &file_as_list, TRUE); + } +} + +static void +report_broken_symbolic_link (GtkWindow *parent_window, + NautilusFile *file) +{ + char *target_path; + char *display_name; + char *detail; + GtkWidget *dialog; + TrashBrokenSymbolicLinkData *data; + + gboolean can_trash; + + g_assert (nautilus_file_is_broken_symbolic_link (file)); + + display_name = nautilus_file_get_display_name (file); + can_trash = nautilus_file_can_trash (file) && !nautilus_file_is_in_trash (file); + + target_path = nautilus_file_get_symbolic_link_target_path (file); + if (target_path == NULL) + { + detail = g_strdup (_("This link cannot be used because it has no target.")); + } + else + { + detail = g_strdup_printf (_("This link cannot be used because its target " + "“%s” doesn’t exist."), target_path); + } + + if (can_trash) + { + dialog = adw_message_dialog_new (parent_window, NULL, detail); + adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog), + _("The link “%s” is broken. Move it to Trash?"), + display_name); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "move-to-trash", _("Mo_ve to Trash"), + NULL); + } + else + { + dialog = adw_message_dialog_new (parent_window, NULL, detail); + adw_message_dialog_format_heading (ADW_MESSAGE_DIALOG (dialog), + _("The link “%s” is broken."), + display_name); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("Cancel")); + } + g_free (display_name); + + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "cancel"); + + /* Make this modal to avoid problems with reffing the view & file + * to keep them around in case the view changes, which would then + * cause the old view not to be destroyed, which would cause its + * merged Bonobo items not to be un-merged. Maybe we need to unmerge + * explicitly when disconnecting views instead of relying on the + * unmerge in Destroy. But since BonoboUIHandler is probably going + * to change wildly, I don't want to mess with this now. + */ + + data = g_new0 (TrashBrokenSymbolicLinkData, 1); + data->parent_window = parent_window; + data->file = file; + + g_signal_connect (G_OBJECT (dialog), + "response", + G_CALLBACK (trash_symbolic_link_cb), + data); + + g_free (target_path); + g_free (detail); +} + +static ActivationAction +get_default_executable_text_file_action (void) +{ + return ACTIVATION_ACTION_OPEN_IN_APPLICATION; +} + +static ActivationAction +get_activation_action (NautilusFile *file) +{ + ActivationAction action; + char *activation_uri; + gboolean handles_extract = FALSE; + g_autoptr (GAppInfo) app_info = NULL; + const gchar *app_id; + + app_info = nautilus_mime_get_default_application_for_file (file); + if (app_info != NULL) + { + app_id = g_app_info_get_id (app_info); + handles_extract = g_strcmp0 (app_id, NAUTILUS_DESKTOP_ID) == 0; + } + if (handles_extract && nautilus_file_is_archive (file)) + { + return ACTIVATION_ACTION_EXTRACT; + } + + activation_uri = nautilus_file_get_activation_uri (file); + if (activation_uri == NULL) + { + activation_uri = nautilus_file_get_uri (file); + } + + action = ACTIVATION_ACTION_DO_NOTHING; + if (nautilus_file_is_launchable (file)) + { + char *executable_path; + + action = ACTIVATION_ACTION_LAUNCH; + + executable_path = g_filename_from_uri (activation_uri, NULL, NULL); + if (!executable_path) + { + action = ACTIVATION_ACTION_DO_NOTHING; + } + else if (nautilus_file_contains_text (file)) + { + action = get_default_executable_text_file_action (); + } + g_free (executable_path); + } + + if (action == ACTIVATION_ACTION_DO_NOTHING) + { + if (nautilus_file_opens_in_view (file)) + { + action = ACTIVATION_ACTION_OPEN_IN_VIEW; + } + else + { + action = ACTIVATION_ACTION_OPEN_IN_APPLICATION; + } + } + g_free (activation_uri); + + return action; +} + +gboolean +nautilus_mime_file_extracts (NautilusFile *file) +{ + return get_activation_action (file) == ACTIVATION_ACTION_EXTRACT; +} + +gboolean +nautilus_mime_file_launches (NautilusFile *file) +{ + ActivationAction activation_action; + + activation_action = get_activation_action (file); + + return (activation_action == ACTIVATION_ACTION_LAUNCH); +} + +gboolean +nautilus_mime_file_opens_in_external_app (NautilusFile *file) +{ + ActivationAction activation_action; + + activation_action = get_activation_action (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) +{ + GError *error; + + error = nautilus_file_get_file_info_error (file); + return + error != NULL && + error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_CANCELLED; +} + +static gboolean +file_was_not_mounted (NautilusFile *file) +{ + GError *error; + + error = nautilus_file_get_file_info_error (file); + return + error != NULL && + error->domain == G_IO_ERROR && + error->code == G_IO_ERROR_NOT_MOUNTED; +} + +static void +activation_parameters_free (ActivateParameters *parameters) +{ + if (parameters->timed_wait_active) + { + eel_timed_wait_stop (cancel_activate_callback, parameters); + } + + if (parameters->slot) + { + g_object_remove_weak_pointer (G_OBJECT (parameters->slot), (gpointer *) ¶meters->slot); + } + if (parameters->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *) ¶meters->parent_window); + } + g_object_unref (parameters->cancellable); + launch_location_list_free (parameters->locations); + nautilus_file_list_free (parameters->mountables); + nautilus_file_list_free (parameters->start_mountables); + nautilus_file_list_free (parameters->not_mounted); + g_free (parameters->activation_directory); + g_free (parameters->timed_wait_prompt); + g_assert (parameters->files_handle == NULL); + g_clear_pointer (¶meters->open_in_view_files, g_queue_free); + g_clear_pointer (¶meters->open_in_app_uris, g_queue_free); + g_clear_pointer (¶meters->launch_files, g_queue_free); + g_clear_pointer (¶meters->launch_in_terminal_files, g_queue_free); + g_list_free (parameters->open_in_app_parameters); + g_list_free (parameters->unhandled_open_in_app_uris); + g_free (parameters); +} + +static void +application_launch_async_parameters_free (ApplicationLaunchAsyncParameters *parameters) +{ + g_queue_free (parameters->uris); + activation_parameters_free (parameters->activation_params); + + g_free (parameters); +} + +static void +cancel_activate_callback (gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + + parameters->timed_wait_active = FALSE; + + g_cancellable_cancel (parameters->cancellable); + + if (parameters->files_handle) + { + nautilus_file_list_cancel_call_when_ready (parameters->files_handle); + parameters->files_handle = NULL; + activation_parameters_free (parameters); + } +} + +static void +activation_start_timed_cancel (ActivateParameters *parameters) +{ + parameters->timed_wait_active = TRUE; + eel_timed_wait_start_with_duration + (DELAY_UNTIL_CANCEL_MSECS, + cancel_activate_callback, + parameters, + parameters->timed_wait_prompt, + parameters->parent_window); +} + +static void +pause_activation_timed_cancel (ActivateParameters *parameters) +{ + if (parameters->timed_wait_active) + { + eel_timed_wait_stop (cancel_activate_callback, parameters); + parameters->timed_wait_active = FALSE; + } +} + +static void +unpause_activation_timed_cancel (ActivateParameters *parameters) +{ + if (!parameters->timed_wait_active) + { + activation_start_timed_cancel (parameters); + } +} + + +static void +activate_mount_op_active (GtkMountOperation *operation, + GParamSpec *pspec, + ActivateParameters *parameters) +{ + gboolean is_active; + + g_object_get (operation, "is-showing", &is_active, NULL); + + if (is_active) + { + pause_activation_timed_cancel (parameters); + } + else + { + unpause_activation_timed_cancel (parameters); + } +} + +static void +on_confirm_multiple_windows_response (GtkDialog *dialog, + gchar *response, + ActivateParameters *parameters) +{ + if (g_strcmp0 (response, "open-all") == 0) + { + unpause_activation_timed_cancel (parameters); + activate_files_internal (parameters); + } + else + { + activation_parameters_free (parameters); + } +} + +static void +show_confirm_multiple (ActivateParameters *parameters, + int window_count, + int tab_count) +{ + GtkWindow *parent_window = parameters->parent_window; + GtkWidget *dialog; + char *prompt; + char *detail; + + prompt = _("Are you sure you want to open all files?"); + if (tab_count > 0 && window_count > 0) + { + int count = tab_count + window_count; + detail = g_strdup_printf (ngettext ("This will open %d separate tab and window.", + "This will open %d separate tabs and windows.", count), count); + } + else if (tab_count > 0) + { + detail = g_strdup_printf (ngettext ("This will open %d separate tab.", + "This will open %d separate tabs.", tab_count), tab_count); + } + else + { + detail = g_strdup_printf (ngettext ("This will open %d separate window.", + "This will open %d separate windows.", window_count), window_count); + } + + dialog = adw_message_dialog_new (parent_window, prompt, detail); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "open-all", _("_Open All"), + NULL); + + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "open-all"); + + g_signal_connect (dialog, "response", + G_CALLBACK (on_confirm_multiple_windows_response), parameters); + gtk_window_present (GTK_WINDOW (dialog)); + + g_free (detail); +} + +typedef struct +{ + NautilusWindowSlot *slot; + GtkWindow *parent_window; + NautilusFile *file; + GList *files; + NautilusOpenFlags flags; + char *activation_directory; + gboolean user_confirmation; + char *uri; + GDBusProxy *proxy; + GtkWidget *dialog; +} ActivateParametersInstall; + +static void +activate_parameters_install_free (ActivateParametersInstall *parameters_install) +{ + if (parameters_install->slot) + { + g_object_remove_weak_pointer (G_OBJECT (parameters_install->slot), (gpointer *) ¶meters_install->slot); + } + if (parameters_install->parent_window) + { + g_object_remove_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *) ¶meters_install->parent_window); + } + + if (parameters_install->proxy != NULL) + { + g_object_unref (parameters_install->proxy); + } + + nautilus_file_unref (parameters_install->file); + nautilus_file_list_free (parameters_install->files); + g_free (parameters_install->activation_directory); + g_free (parameters_install->uri); + g_free (parameters_install); +} + +static char * +get_application_no_mime_type_handler_message (NautilusFile *file, + char *uri) +{ + char *uri_for_display; + char *name; + char *error_message; + + name = nautilus_file_get_display_name (file); + + /* Truncate the URI so it doesn't get insanely wide. Note that even + * though the dialog uses wrapped text, if the URI doesn't contain + * white space then the text-wrapping code is too stupid to wrap it. + */ + uri_for_display = eel_str_middle_truncate (name, MAX_URI_IN_DIALOG_LENGTH); + error_message = g_strdup_printf (_("Could Not Display “%s”"), uri_for_display); + g_free (uri_for_display); + g_free (name); + + return error_message; +} + +static void +open_with_response_cb (GtkDialog *dialog, + gint response_id, + gpointer user_data) +{ + GtkWindow *parent_window; + NautilusFile *file; + GList files; + GAppInfo *info; + ActivateParametersInstall *parameters = user_data; + + if (response_id != GTK_RESPONSE_OK) + { + gtk_window_destroy (GTK_WINDOW (dialog)); + return; + } + + parent_window = parameters->parent_window; + file = g_object_get_data (G_OBJECT (dialog), "mime-action:file"); + info = gtk_app_chooser_get_app_info (GTK_APP_CHOOSER (dialog)); + + gtk_window_destroy (GTK_WINDOW (dialog)); + + g_signal_emit_by_name (nautilus_signaller_get_current (), "mime-data-changed"); + + files.next = NULL; + files.prev = NULL; + files.data = file; + nautilus_launch_application (info, &files, parent_window); + + g_object_unref (info); + + activate_parameters_install_free (parameters); +} + +static void +choose_program (GtkDialog *message_dialog, + gchar *response, + gpointer callback_data) +{ + GtkWidget *dialog; + NautilusFile *file; + GFile *location; + ActivateParametersInstall *parameters = callback_data; + + if (g_strcmp0 (response, "select-application") != 0) + { + activate_parameters_install_free (parameters); + return; + } + + file = g_object_get_data (G_OBJECT (message_dialog), "mime-action:file"); + + g_assert (NAUTILUS_IS_FILE (file)); + + location = nautilus_file_get_location (file); + nautilus_file_ref (file); + + /* Destroy the message dialog after ref:ing the file */ + gtk_window_destroy (GTK_WINDOW (message_dialog)); + + dialog = gtk_app_chooser_dialog_new (parameters->parent_window, + GTK_DIALOG_MODAL, + location); + g_object_set_data_full (G_OBJECT (dialog), + "mime-action:file", + nautilus_file_ref (file), + (GDestroyNotify) nautilus_file_unref); + + gtk_widget_show (dialog); + + g_signal_connect (dialog, + "response", + G_CALLBACK (open_with_response_cb), + parameters); + + g_object_unref (location); + nautilus_file_unref (file); +} + +static void +show_unhandled_type_error (ActivateParametersInstall *parameters) +{ + GtkWidget *dialog; + g_autofree char *body = NULL; + g_autofree char *content_type_description = NULL; + + char *mime_type = nautilus_file_get_mime_type (parameters->file); + char *error_message = get_application_no_mime_type_handler_message (parameters->file, parameters->uri); + + if (g_content_type_is_unknown (mime_type)) + { + body = g_strdup (_("The file is of an unknown type")); + } + else + { + content_type_description = g_content_type_get_description (mime_type); + body = g_strdup_printf (_("There is no application installed for “%s” files"), content_type_description); + } + + dialog = adw_message_dialog_new (parameters->parent_window, error_message, body); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "select-application", _("_Select Application"), + "ok", _("_OK"), + NULL); + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "ok"); + + g_object_set_data_full (G_OBJECT (dialog), + "mime-action:file", + nautilus_file_ref (parameters->file), + (GDestroyNotify) nautilus_file_unref); + + gtk_window_present (GTK_WINDOW (dialog)); + + g_signal_connect (dialog, "response", + G_CALLBACK (choose_program), parameters); + + g_free (error_message); + g_free (mime_type); +} + +static void +search_for_application_dbus_call_notify_cb (GDBusProxy *proxy, + GAsyncResult *result, + gpointer user_data) +{ + ActivateParametersInstall *parameters_install = user_data; + GVariant *variant; + GError *error = NULL; + + variant = g_dbus_proxy_call_finish (proxy, result, &error); + if (variant == NULL) + { + if (!g_dbus_error_is_remote_error (error) || + g_strcmp0 (g_dbus_error_get_remote_error (error), "org.freedesktop.PackageKit.Modify.Failed") == 0) + { + char *message; + + message = g_strdup_printf ("%s\n%s", + _("There was an internal error trying to search for applications:"), + error->message); + show_dialog (_("Unable to search for application"), + message, + parameters_install->parent_window, + GTK_MESSAGE_ERROR); + g_free (message); + } + else + { + g_warning ("Error while trying to search for applications: %s", + error->message); + } + + g_error_free (error); + activate_parameters_install_free (parameters_install); + return; + } + + g_variant_unref (variant); + + activate_parameters_install_free (parameters_install); +} + +static void +search_for_application_mime_type (ActivateParametersInstall *parameters_install, + const gchar *mime_type) +{ + gchar *desktop_startup_id; + + g_assert (parameters_install->proxy != NULL); + + desktop_startup_id = g_strdup_printf ("_TIME%i", (guint32) GDK_CURRENT_TIME); + + g_dbus_proxy_call (parameters_install->proxy, + "InstallMimeTypes", + g_variant_new_parsed ("([%s], %s, %s, [{%s, %v}])", + mime_type, + "hide-confirm-search", + APPLICATION_ID, + "desktop-startup-id", + g_variant_new_take_string (desktop_startup_id)), + G_DBUS_CALL_FLAGS_NONE, + G_MAXINT /* no timeout */, + NULL /* cancellable */, + (GAsyncReadyCallback) search_for_application_dbus_call_notify_cb, + parameters_install); + + DEBUG ("InstallMimeType method invoked for %s", mime_type); +} + +static void +application_unhandled_file_install (GtkDialog *dialog, + gchar *response, + ActivateParametersInstall *parameters_install) +{ + char *mime_type; + + parameters_install->dialog = NULL; + + if (g_strcmp0 (response, "search-in-software") == 0) + { + mime_type = nautilus_file_get_mime_type (parameters_install->file); + search_for_application_mime_type (parameters_install, mime_type); + g_free (mime_type); + } + else + { + /* free as we're not going to get the async dbus callback */ + activate_parameters_install_free (parameters_install); + } +} + +static void +pk_proxy_appeared_cb (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + ActivateParametersInstall *parameters_install = user_data; + char *mime_type, *name_owner; + char *error_message; + GtkWidget *dialog; + GDBusProxy *proxy; + GError *error = NULL; + g_autofree char *content_type_description = NULL; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + name_owner = g_dbus_proxy_get_name_owner (proxy); + + if (error != NULL || name_owner == NULL) + { + g_warning ("Couldn't call Modify on the PackageKit interface: %s", + error != NULL ? error->message : "no owner for PackageKit"); + g_clear_error (&error); + + /* show an unhelpful dialog */ + show_unhandled_type_error (parameters_install); + + return; + } + + g_free (name_owner); + + mime_type = nautilus_file_get_mime_type (parameters_install->file); + content_type_description = g_content_type_get_description (mime_type); + error_message = get_application_no_mime_type_handler_message (parameters_install->file, + parameters_install->uri); + /* use a custom dialog to prompt the user to install new software */ + dialog = adw_message_dialog_new (parameters_install->parent_window, error_message, NULL); + adw_message_dialog_add_responses (ADW_MESSAGE_DIALOG (dialog), + "cancel", _("_Cancel"), + "search-in-software", _("_Search in Software"), + NULL); + adw_message_dialog_format_body (ADW_MESSAGE_DIALOG (dialog), + _("There is no application installed for “%s” files. " + "Do you want to search for an application to open this file?"), + content_type_description); + + adw_message_dialog_set_default_response (ADW_MESSAGE_DIALOG (dialog), "search-in-software"); + + parameters_install->dialog = dialog; + parameters_install->proxy = proxy; + + g_signal_connect (dialog, "response", + G_CALLBACK (application_unhandled_file_install), + parameters_install); + gtk_window_present (GTK_WINDOW (dialog)); + g_free (mime_type); +} + +static void +application_unhandled_uri (ActivateParameters *parameters, + char *uri) +{ + gboolean show_install_mime; + char *mime_type; + NautilusFile *file; + ActivateParametersInstall *parameters_install; + + file = nautilus_file_get_by_uri (uri); + + mime_type = nautilus_file_get_mime_type (file); + + /* copy the parts of parameters we are interested in as the orignal will be unref'd */ + parameters_install = g_new0 (ActivateParametersInstall, 1); + parameters_install->slot = parameters->slot; + g_object_add_weak_pointer (G_OBJECT (parameters_install->slot), (gpointer *) ¶meters_install->slot); + if (parameters->parent_window) + { + parameters_install->parent_window = parameters->parent_window; + g_object_add_weak_pointer (G_OBJECT (parameters_install->parent_window), (gpointer *) ¶meters_install->parent_window); + } + parameters_install->activation_directory = g_strdup (parameters->activation_directory); + parameters_install->file = file; + parameters_install->files = get_file_list_for_launch_locations (parameters->locations); + parameters_install->flags = parameters->flags; + parameters_install->user_confirmation = parameters->user_confirmation; + parameters_install->uri = g_strdup (uri); + +#ifdef ENABLE_PACKAGEKIT + /* allow an admin to disable the PackageKit search functionality */ + show_install_mime = g_settings_get_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_INSTALL_MIME_ACTIVATION); +#else + /* we have no install functionality */ + show_install_mime = FALSE; +#endif + /* There is no use trying to look for handlers of application/octet-stream */ + if (g_content_type_is_unknown (mime_type)) + { + show_install_mime = FALSE; + } + + g_free (mime_type); + + if (!show_install_mime) + { + goto out; + } + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.PackageKit", + "/org/freedesktop/PackageKit", + "org.freedesktop.PackageKit.Modify2", + NULL, + pk_proxy_appeared_cb, + parameters_install); + + return; + +out: + /* show an unhelpful dialog */ + show_unhandled_type_error (parameters_install); +} + +static void +launch_default_for_uris_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ApplicationLaunchAsyncParameters *params; + ActivateParameters *activation_params; + char *uri; + g_autoptr (GError) error = NULL; + + params = user_data; + activation_params = params->activation_params; + uri = g_queue_pop_head (params->uris); + + nautilus_launch_default_for_uri_finish (res, &error); + if (error == NULL) + { + gtk_recent_manager_add_item (gtk_recent_manager_get_default (), 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, + launch_default_for_uris_callback, + params); + } + else + { + application_launch_async_parameters_free (params); + } +} + +static void +activate_files (ActivateParameters *parameters) +{ + NautilusFile *file; + int count; + gint num_windows = 0; + gint num_tabs = 0; + GList *l; + ActivationAction action; + + parameters->launch_files = g_queue_new (); + parameters->launch_in_terminal_files = g_queue_new (); + parameters->open_in_view_files = g_queue_new (); + parameters->open_in_app_uris = g_queue_new (); + + for (l = parameters->locations; l != NULL; l = l->next) + { + LaunchLocation *location; + + location = l->data; + file = location->file; + + if (file_was_cancelled (file)) + { + continue; + } + + action = get_activation_action (file); + + switch (action) + { + case ACTIVATION_ACTION_LAUNCH: + { + g_queue_push_tail (parameters->launch_files, file); + } + break; + + case ACTIVATION_ACTION_LAUNCH_IN_TERMINAL: + { + g_queue_push_tail (parameters->launch_in_terminal_files, file); + } + break; + + case ACTIVATION_ACTION_OPEN_IN_VIEW: + { + g_queue_push_tail (parameters->open_in_view_files, file); + } + break; + + case ACTIVATION_ACTION_OPEN_IN_APPLICATION: + { + g_queue_push_tail (parameters->open_in_app_uris, location->uri); + } + break; + + case ACTIVATION_ACTION_DO_NOTHING: + { + } + break; + + case ACTIVATION_ACTION_EXTRACT: + { + /* Extraction of files should be handled in the view */ + g_assert_not_reached (); + } + break; + } + } + + count = g_queue_get_length (parameters->open_in_view_files); + if (count > 1) + { + if ((parameters->flags & NAUTILUS_OPEN_FLAG_NEW_WINDOW) == 0) + { + parameters->flags |= NAUTILUS_OPEN_FLAG_NEW_TAB; + num_tabs += count; + } + else + { + parameters->flags |= NAUTILUS_OPEN_FLAG_NEW_WINDOW; + num_windows += count; + } + } + + if (parameters->open_in_app_uris != NULL) + { + if (nautilus_application_is_sandboxed ()) + { + num_windows += g_queue_get_length (parameters->open_in_app_uris); + } + else + { + parameters->open_in_app_parameters = make_activation_parameters (g_queue_peek_head_link (parameters->open_in_app_uris), + ¶meters->unhandled_open_in_app_uris); + num_windows += g_list_length (parameters->open_in_app_parameters); + num_windows += g_list_length (parameters->unhandled_open_in_app_uris); + } + } + + num_windows += g_queue_get_length (parameters->launch_files); + num_windows += g_queue_get_length (parameters->launch_in_terminal_files); + + if (parameters->user_confirmation && + num_tabs + num_windows > SILENT_OPEN_LIMIT) + { + pause_activation_timed_cancel (parameters); + show_confirm_multiple (parameters, num_windows, num_tabs); + } + else + { + activate_files_internal (parameters); + } +} + +static void +activate_files_internal (ActivateParameters *parameters) +{ + NautilusFile *file; + ApplicationLaunchParameters *one_parameters; + g_autofree char *old_working_dir = NULL; + GdkDisplay *display; + GList *l; + + if (parameters->activation_directory && + (!g_queue_is_empty (parameters->launch_files) || + !g_queue_is_empty (parameters->launch_in_terminal_files))) + { + old_working_dir = g_get_current_dir (); + g_chdir (parameters->activation_directory); + } + + display = gtk_widget_get_display (GTK_WIDGET (parameters->parent_window)); + for (l = g_queue_peek_head_link (parameters->launch_files); l != NULL; l = l->next) + { + g_autofree char *uri = NULL; + g_autofree char *executable_path = NULL; + g_autofree char *quoted_path = NULL; + + file = NAUTILUS_FILE (l->data); + + uri = nautilus_file_get_activation_uri (file); + executable_path = g_filename_from_uri (uri, NULL, NULL); + quoted_path = g_shell_quote (executable_path); + + DEBUG ("Launching file path %s", quoted_path); + + nautilus_launch_application_from_command (display, quoted_path, FALSE, NULL); + } + + for (l = g_queue_peek_head_link (parameters->launch_in_terminal_files); l != NULL; l = l->next) + { + g_autofree char *uri = NULL; + g_autofree char *executable_path = NULL; + g_autofree char *quoted_path = NULL; + + file = NAUTILUS_FILE (l->data); + + uri = nautilus_file_get_activation_uri (file); + executable_path = g_filename_from_uri (uri, NULL, NULL); + quoted_path = g_shell_quote (executable_path); + + DEBUG ("Launching in terminal file quoted path %s", quoted_path); + + nautilus_launch_application_from_command (display, quoted_path, TRUE, NULL); + } + + if (old_working_dir != NULL) + { + g_chdir (old_working_dir); + } + + if (parameters->slot != NULL) + { + if ((parameters->flags & NAUTILUS_OPEN_FLAG_NEW_TAB) != 0) + { + /* When inserting N tabs after the current one, + * we first open tab N, then tab N-1, ..., then tab 0. + * Each of them is appended to the current tab, i.e. + * prepended to the list of tabs to open. + */ + g_queue_reverse (parameters->open_in_view_files); + } + + for (l = g_queue_peek_head_link (parameters->open_in_view_files); l != NULL; l = l->next) + { + g_autofree char *uri = NULL; + g_autoptr (GFile) location = NULL; + g_autoptr (GFile) location_with_permissions = NULL; + /* The ui should ask for navigation or object windows + * depending on what the current one is */ + file = NAUTILUS_FILE (l->data); + uri = nautilus_file_get_activation_uri (file); + location = g_file_new_for_uri (uri); + if (g_file_is_native (location) && + (nautilus_file_is_in_admin (file) || + !nautilus_file_can_read (file) || + !nautilus_file_can_execute (file))) + { + g_autofree gchar *file_path = NULL; + + g_free (uri); + + file_path = g_file_get_path (location); + uri = g_strconcat ("admin://", file_path, NULL); + } + + location_with_permissions = g_file_new_for_uri (uri); + /* FIXME: we need to pass the parent_window, but we only use it for the current active window, + * which nautilus-application should take care of. However is not working and creating regressions + * in some cases. Until we figure out what's going on, continue to use the parameters->slot + * to make splicit the window we want to use for activating the files */ + nautilus_application_open_location_full (NAUTILUS_APPLICATION (g_application_get_default ()), + location_with_permissions, parameters->flags, NULL, NULL, parameters->slot); + } + } + + if (!g_queue_is_empty (parameters->open_in_app_uris) && nautilus_application_is_sandboxed ()) + { + const char *uri; + ApplicationLaunchAsyncParameters *async_params; + + uri = g_queue_peek_head (parameters->open_in_app_uris); + + async_params = g_new0 (ApplicationLaunchAsyncParameters, 1); + async_params->activation_params = parameters; + async_params->uris = g_steal_pointer (¶meters->open_in_app_uris); + + nautilus_launch_default_for_uri_async (uri, + parameters->parent_window, + parameters->cancellable, + launch_default_for_uris_callback, + async_params); + return; + } + + if (!g_queue_is_empty (parameters->open_in_app_uris)) + { + for (l = parameters->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 = parameters->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 +activation_mount_not_mounted_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + ActivateParameters *parameters = user_data; + GError *error; + NautilusFile *file; + LaunchLocation *loc; + + file = parameters->not_mounted->data; + + error = NULL; + if (!g_file_mount_enclosing_volume_finish (G_FILE (source_object), res, &error)) + { + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED)) + { + show_dialog (_("Unable to access location"), + error->message, + parameters->parent_window, + GTK_MESSAGE_ERROR); + } + + if (error->domain != G_IO_ERROR || + error->code != G_IO_ERROR_ALREADY_MOUNTED) + { + loc = find_launch_location_for_file (parameters->locations, + file); + if (loc) + { + parameters->locations = + g_list_remove (parameters->locations, loc); + launch_location_free (loc); + } + } + + g_error_free (error); + } + + parameters->not_mounted = g_list_delete_link (parameters->not_mounted, + parameters->not_mounted); + nautilus_file_unref (file); + + activation_mount_not_mounted (parameters); +} + +static void +activation_mount_not_mounted (ActivateParameters *parameters) +{ + NautilusFile *file; + GFile *location; + LaunchLocation *loc; + GMountOperation *mount_op; + GList *l, *next, *files; + + if (parameters->not_mounted != NULL) + { + file = parameters->not_mounted->data; + mount_op = gtk_mount_operation_new (parameters->parent_window); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + g_signal_connect (mount_op, "notify::is-showing", + G_CALLBACK (activate_mount_op_active), parameters); + location = nautilus_file_get_location (file); + g_file_mount_enclosing_volume (location, 0, mount_op, parameters->cancellable, + activation_mount_not_mounted_callback, parameters); + g_object_unref (location); + /* unref mount_op here - g_file_mount_enclosing_volume() does ref for itself */ + g_object_unref (mount_op); + return; + } + + parameters->tried_mounting = TRUE; + + if (parameters->locations == NULL) + { + activation_parameters_free (parameters); + return; + } + + /* once the mount is finished, refresh all attributes + * - fixes new windows not appearing after successful mount + */ + for (l = parameters->locations; l != NULL; l = next) + { + loc = l->data; + next = l->next; + nautilus_file_invalidate_all_attributes (loc->file); + } + + files = get_file_list_for_launch_locations (parameters->locations); + nautilus_file_list_call_when_ready + (files, + nautilus_mime_actions_get_required_file_attributes (), + ¶meters->files_handle, + activate_callback, parameters); + nautilus_file_list_free (files); +} + + +static void +activate_callback (GList *files, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + GList *l, *next; + NautilusFile *file; + LaunchLocation *location; + + parameters->files_handle = NULL; + + for (l = parameters->locations; l != NULL; l = next) + { + location = l->data; + file = location->file; + next = l->next; + + if (file_was_cancelled (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + + if (file_was_not_mounted (file)) + { + if (parameters->tried_mounting) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + } + else + { + parameters->not_mounted = g_list_prepend (parameters->not_mounted, + nautilus_file_ref (file)); + } + continue; + } + } + + + if (parameters->not_mounted != NULL) + { + activation_mount_not_mounted (parameters); + } + else + { + activate_files (parameters); + } +} + +static void +activate_activation_uris_ready_callback (GList *files_ignore, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + GList *l, *next, *files; + NautilusFile *file; + LaunchLocation *location; + + parameters->files_handle = NULL; + + for (l = parameters->locations; l != NULL; l = next) + { + location = l->data; + file = location->file; + next = l->next; + + if (file_was_cancelled (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + + if (nautilus_file_is_broken_symbolic_link (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + pause_activation_timed_cancel (parameters); + report_broken_symbolic_link (parameters->parent_window, file); + unpause_activation_timed_cancel (parameters); + continue; + } + + if (nautilus_file_get_file_type (file) == G_FILE_TYPE_MOUNTABLE && + !nautilus_file_has_activation_uri (file)) + { + /* Don't launch these... There is nothing we + * can do */ + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + } + + if (parameters->locations == NULL) + { + activation_parameters_free (parameters); + return; + } + + /* Convert the files to the actual activation uri files */ + for (l = parameters->locations; l != NULL; l = l->next) + { + char *uri; + location = l->data; + + /* We want the file for the activation URI since we care + * about the attributes for that, not for the original file. + */ + uri = nautilus_file_get_activation_uri (location->file); + if (uri != NULL) + { + launch_location_update_from_uri (location, uri); + } + g_free (uri); + } + + + /* get the parameters for the actual files */ + files = get_file_list_for_launch_locations (parameters->locations); + nautilus_file_list_call_when_ready + (files, + nautilus_mime_actions_get_required_file_attributes (), + ¶meters->files_handle, + activate_callback, parameters); + nautilus_file_list_free (files); +} + +static void +activate_regular_files (ActivateParameters *parameters) +{ + GList *l, *files; + NautilusFile *file; + LaunchLocation *location; + + /* link target info might be stale, re-read it */ + for (l = parameters->locations; l != NULL; l = l->next) + { + location = l->data; + file = location->file; + + if (file_was_cancelled (file)) + { + launch_location_free (location); + parameters->locations = g_list_delete_link (parameters->locations, l); + continue; + } + } + + if (parameters->locations == NULL) + { + activation_parameters_free (parameters); + return; + } + + files = get_file_list_for_launch_locations (parameters->locations); + nautilus_file_list_call_when_ready + (files, nautilus_mime_actions_get_required_file_attributes (), + ¶meters->files_handle, + activate_activation_uris_ready_callback, parameters); + nautilus_file_list_free (files); +} + +static void +activation_mountable_mounted (NautilusFile *file, + GFile *result_location, + GError *error, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + NautilusFile *target_file; + LaunchLocation *location; + + /* Remove from list of files that have to be mounted */ + parameters->mountables = g_list_remove (parameters->mountables, file); + nautilus_file_unref (file); + + + if (error == NULL) + { + /* Replace file with the result of the mount */ + target_file = nautilus_file_get (result_location); + + location = find_launch_location_for_file (parameters->locations, + file); + if (location) + { + launch_location_update_from_file (location, target_file); + } + nautilus_file_unref (target_file); + } + else + { + /* Remove failed file */ + + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED)) + { + location = find_launch_location_for_file (parameters->locations, + file); + if (location) + { + parameters->locations = + g_list_remove (parameters->locations, + location); + launch_location_free (location); + } + } + + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED && + error->code != G_IO_ERROR_ALREADY_MOUNTED)) + { + show_dialog (_("Unable to access location"), + error->message, + parameters->parent_window, + GTK_MESSAGE_ERROR); + } + + if (error->code == G_IO_ERROR_CANCELLED) + { + activation_parameters_free (parameters); + return; + } + } + + /* Mount more mountables */ + activation_mount_mountables (parameters); +} + + +static void +activation_mount_mountables (ActivateParameters *parameters) +{ + NautilusFile *file; + GMountOperation *mount_op; + + if (parameters->mountables != NULL) + { + file = parameters->mountables->data; + mount_op = gtk_mount_operation_new (parameters->parent_window); + g_mount_operation_set_password_save (mount_op, G_PASSWORD_SAVE_FOR_SESSION); + g_signal_connect (mount_op, "notify::is-showing", + G_CALLBACK (activate_mount_op_active), parameters); + nautilus_file_mount (file, + mount_op, + parameters->cancellable, + activation_mountable_mounted, + parameters); + g_object_unref (mount_op); + return; + } + + if (parameters->mountables == NULL && parameters->start_mountables == NULL) + { + activate_regular_files (parameters); + } +} + + +static void +activation_mountable_started (NautilusFile *file, + GFile *gfile_of_file, + GError *error, + gpointer callback_data) +{ + ActivateParameters *parameters = callback_data; + LaunchLocation *location; + + /* Remove from list of files that have to be mounted */ + parameters->start_mountables = g_list_remove (parameters->start_mountables, file); + nautilus_file_unref (file); + + if (error == NULL) + { + /* Remove file */ + location = find_launch_location_for_file (parameters->locations, file); + if (location != NULL) + { + parameters->locations = g_list_remove (parameters->locations, location); + launch_location_free (location); + } + } + else + { + /* Remove failed file */ + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_FAILED_HANDLED)) + { + location = find_launch_location_for_file (parameters->locations, + file); + if (location) + { + parameters->locations = + g_list_remove (parameters->locations, + location); + launch_location_free (location); + } + } + + if (error->domain != G_IO_ERROR || + (error->code != G_IO_ERROR_CANCELLED && + error->code != G_IO_ERROR_FAILED_HANDLED)) + { + show_dialog (_("Unable to start location"), + error->message, + parameters->parent_window, + GTK_MESSAGE_ERROR); + } + + if (error->code == G_IO_ERROR_CANCELLED) + { + activation_parameters_free (parameters); + return; + } + } + + /* Start more mountables */ + activation_start_mountables (parameters); +} + +static void +activation_start_mountables (ActivateParameters *parameters) +{ + NautilusFile *file; + GMountOperation *start_op; + + if (parameters->start_mountables != NULL) + { + file = parameters->start_mountables->data; + start_op = gtk_mount_operation_new (parameters->parent_window); + g_signal_connect (start_op, "notify::is-showing", + G_CALLBACK (activate_mount_op_active), parameters); + nautilus_file_start (file, + start_op, + parameters->cancellable, + activation_mountable_started, + parameters); + g_object_unref (start_op); + return; + } + + if (parameters->mountables == NULL && parameters->start_mountables == NULL) + { + activate_regular_files (parameters); + } +} + +/** + * nautilus_mime_activate_files: + * + * Activate a list of files. Each one might launch with an application or + * with a component. This is normally called only by subclasses. + * @view: FMDirectoryView in question. + * @files: A GList of NautilusFiles to activate. + * + **/ +void +nautilus_mime_activate_files (GtkWindow *parent_window, + NautilusWindowSlot *slot, + GList *files, + const char *launch_directory, + NautilusOpenFlags flags, + gboolean user_confirmation) +{ + ActivateParameters *parameters; + char *file_name; + int file_count; + GList *l, *next; + NautilusFile *file; + LaunchLocation *location; + + if (files == NULL) + { + return; + } + + DEBUG_FILES (files, "Calling activate_files() with files:"); + + parameters = g_new0 (ActivateParameters, 1); + parameters->slot = slot; + g_object_add_weak_pointer (G_OBJECT (parameters->slot), (gpointer *) ¶meters->slot); + if (parent_window) + { + parameters->parent_window = parent_window; + g_object_add_weak_pointer (G_OBJECT (parameters->parent_window), (gpointer *) ¶meters->parent_window); + } + parameters->cancellable = g_cancellable_new (); + parameters->activation_directory = g_strdup (launch_directory); + parameters->locations = launch_locations_from_file_list (files); + parameters->flags = flags; + parameters->user_confirmation = user_confirmation; + + file_count = g_list_length (files); + if (file_count == 1) + { + file_name = nautilus_file_get_display_name (files->data); + parameters->timed_wait_prompt = g_strdup_printf (_("Opening “%s”."), file_name); + g_free (file_name); + } + else + { + parameters->timed_wait_prompt = g_strdup_printf (ngettext ("Opening %d item.", + "Opening %d items.", + file_count), + file_count); + } + + + for (l = parameters->locations; l != NULL; l = next) + { + location = l->data; + file = location->file; + next = l->next; + + if (nautilus_file_can_mount (file)) + { + parameters->mountables = g_list_prepend (parameters->mountables, + nautilus_file_ref (file)); + } + + if (nautilus_file_can_start (file)) + { + parameters->start_mountables = g_list_prepend (parameters->start_mountables, + nautilus_file_ref (file)); + } + } + + activation_start_timed_cancel (parameters); + if (parameters->mountables != NULL) + { + activation_mount_mountables (parameters); + } + if (parameters->start_mountables != NULL) + { + activation_start_mountables (parameters); + } + if (parameters->mountables == NULL && parameters->start_mountables == NULL) + { + activate_regular_files (parameters); + } +} + +/** + * nautilus_mime_activate_file: + * + * Activate a file in this view. This might involve switching the displayed + * location for the current window, or launching an application. + * @view: FMDirectoryView in question. + * @file: A NautilusFile representing the file in this view to activate. + * @use_new_window: Should this item be opened in a new window? + * + **/ + +void +nautilus_mime_activate_file (GtkWindow *parent_window, + NautilusWindowSlot *slot, + NautilusFile *file, + const char *launch_directory, + NautilusOpenFlags flags) +{ + GList *files; + + g_return_if_fail (NAUTILUS_IS_FILE (file)); + + files = g_list_prepend (NULL, file); + nautilus_mime_activate_files (parent_window, slot, files, launch_directory, flags, FALSE); + g_list_free (files); +} + +gint +nautilus_mime_types_get_number_of_groups (void) +{ + return G_N_ELEMENTS (mimetype_groups); +} + +const gchar * +nautilus_mime_types_group_get_name (gint group_index) +{ + g_return_val_if_fail (group_index < G_N_ELEMENTS (mimetype_groups), NULL); + + return gettext (mimetype_groups[group_index].name); +} + +GPtrArray * +nautilus_mime_types_group_get_mimetypes (gint group_index) +{ + GStrv group; + GPtrArray *mimetypes; + + g_return_val_if_fail (group_index < G_N_ELEMENTS (mimetype_groups), NULL); + + group = mimetype_groups[group_index].mimetypes; + mimetypes = g_ptr_array_new_full (g_strv_length (group), g_free); + + /* Setup the new mimetypes set */ + for (gint i = 0; group[i] != NULL; i++) + { + g_ptr_array_add (mimetypes, g_strdup (group[i])); + } + + return mimetypes; +} |