/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* caja-mime-actions.c - uri-specific versions of mime action functions Copyright (C) 2000, 2001 Eazel, Inc. The Mate 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 Mate 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 Mate Library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. Authors: Maciej Stachowiak <mjs@eazel.com> */ #include <config.h> #include "caja-mime-actions.h" #include <eel/eel-glib-extensions.h> #include <eel/eel-stock-dialogs.h> #include <eel/eel-string.h> #include <glib/gi18n.h> #include <glib/gstdio.h> #include <string.h> #include <gdk/gdkx.h> #include "caja-file-attributes.h" #include "caja-file.h" #include "caja-autorun.h" #include "caja-file-operations.h" #include "caja-metadata.h" #include "caja-program-choosing.h" #include "caja-desktop-icon-file.h" #include "caja-global-preferences.h" #include "caja-debug-log.h" #include "caja-open-with-dialog.h" #include <src/glibcompat.h> /* for g_list_free_full */ typedef enum { ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE, ACTIVATION_ACTION_ASK, ACTIVATION_ACTION_LAUNCH, ACTIVATION_ACTION_LAUNCH_IN_TERMINAL, ACTIVATION_ACTION_OPEN_IN_VIEW, ACTIVATION_ACTION_OPEN_IN_APPLICATION, ACTIVATION_ACTION_DO_NOTHING, } ActivationAction; typedef struct { CajaFile *file; char *uri; } LaunchLocation; typedef struct { GAppInfo *application; GList *uris; } ApplicationLaunchParameters; typedef struct { CajaWindowSlotInfo *slot_info; gpointer window_info; GtkWindow *parent_window; GCancellable *cancellable; GList *locations; GList *mountables; GList *start_mountables; GList *not_mounted; CajaWindowOpenMode mode; CajaWindowOpenFlags flags; char *timed_wait_prompt; gboolean timed_wait_active; CajaFileListHandle *files_handle; gboolean tried_mounting; char *activation_directory; gboolean user_confirmation; } ActivateParameters; /* Number of seconds until cancel dialog shows up */ #define DELAY_UNTIL_CANCEL_MSECS 5000 #define RESPONSE_RUN 1000 #define RESPONSE_DISPLAY 1001 #define RESPONSE_RUN_IN_TERMINAL 1002 #define RESPONSE_MARK_TRUSTED 1003 #define SILENT_WINDOW_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 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) { caja_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, caja_file_ref (location->file)); } return g_list_reverse (files); } static LaunchLocation * launch_location_from_file (CajaFile *file) { LaunchLocation *location; location = g_new (LaunchLocation, 1); location->file = caja_file_ref (file); location->uri = caja_file_get_uri (file); return location; } static void launch_location_update_from_file (LaunchLocation *location, CajaFile *file) { caja_file_unref (location->file); g_free (location->uri); location->file = caja_file_ref (file); location->uri = caja_file_get_uri (file); } static void launch_location_update_from_uri (LaunchLocation *location, const char *uri) { caja_file_unref (location->file); g_free (location->uri); location->file = caja_file_get_by_uri (uri); location->uri = g_strdup (uri); } static LaunchLocation * find_launch_location_for_file (GList *list, CajaFile *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 = eel_g_str_list_copy (uris); 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 GList* filter_caja_handler (GList *apps) { GList *l, *next; GAppInfo *application; const char *id; l = apps; while (l != NULL) { application = (GAppInfo *) l->data; next = l->next; id = g_app_info_get_id (application); if (id != NULL && strcmp (id, "caja-folder-handler.desktop") == 0) { g_object_unref (application); apps = g_list_delete_link (apps, l); } l = next; } return apps; } static GList* filter_non_uri_apps (GList *apps) { GList *l, *next; GAppInfo *app; for (l = apps; l != NULL; l = next) { app = l->data; next = l->next; if (!g_app_info_supports_uris (app)) { apps = g_list_delete_link (apps, l); g_object_unref (app); } } return apps; } static gboolean caja_mime_actions_check_if_required_attributes_ready (CajaFile *file) { CajaFileAttributes attributes; gboolean ready; attributes = caja_mime_actions_get_required_file_attributes (); ready = caja_file_check_if_ready (file, attributes); return ready; } CajaFileAttributes caja_mime_actions_get_required_file_attributes (void) { return CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO; } static gboolean file_has_local_path (CajaFile *file) { GFile *location; char *path; gboolean res; /* Don't only check _is_native, because we want to support using the fuse path */ location = caja_file_get_location (file); if (g_file_is_native (location)) { res = TRUE; } else { path = g_file_get_path (location); res = path != NULL; g_free (path); } g_object_unref (location); return res; } GAppInfo * caja_mime_get_default_application_for_file (CajaFile *file) { GAppInfo *app; char *mime_type; char *uri_scheme; if (!caja_mime_actions_check_if_required_attributes_ready (file)) { return NULL; } mime_type = caja_file_get_mime_type (file); app = g_app_info_get_default_for_type (mime_type, !file_has_local_path (file)); g_free (mime_type); if (app == NULL) { uri_scheme = caja_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 (CajaFile *file_a, CajaFile *file_b) { char *mime_type_a, *mime_type_b; int ret; mime_type_a = caja_file_get_mime_type (file_a); mime_type_b = caja_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 (CajaFile *file_a, CajaFile *file_b) { char *parent_uri_a, *parent_uri_b; int ret; parent_uri_a = caja_file_get_parent_uri (file_a); parent_uri_b = caja_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; } static int application_compare_by_name (const GAppInfo *app_a, const GAppInfo *app_b) { return g_utf8_collate (g_app_info_get_display_name ((GAppInfo *)app_a), g_app_info_get_display_name ((GAppInfo *)app_b)); } static int application_compare_by_id (const GAppInfo *app_a, const GAppInfo *app_b) { const char *id_a, *id_b; id_a = g_app_info_get_id ((GAppInfo *)app_a); id_b = g_app_info_get_id ((GAppInfo *)app_b); if (id_a == NULL && id_b == NULL) { if (g_app_info_equal ((GAppInfo *)app_a, (GAppInfo *)app_b)) { return 0; } if ((gsize)app_a < (gsize) app_b) { return -1; } return 1; } if (id_a == NULL) { return -1; } if (id_b == NULL) { return 1; } return strcmp (id_a, id_b); } GList * caja_mime_get_applications_for_file (CajaFile *file) { char *mime_type; char *uri_scheme; GList *result; GAppInfo *uri_handler; if (!caja_mime_actions_check_if_required_attributes_ready (file)) { return NULL; } mime_type = caja_file_get_mime_type (file); result = g_app_info_get_all_for_type (mime_type); uri_scheme = caja_file_get_uri_scheme (file); if (uri_scheme != NULL) { uri_handler = g_app_info_get_default_for_uri_scheme (uri_scheme); if (uri_handler) { result = g_list_prepend (result, uri_handler); } g_free (uri_scheme); } if (!file_has_local_path (file)) { /* Filter out non-uri supporting apps */ result = filter_non_uri_apps (result); } result = g_list_sort (result, (GCompareFunc) application_compare_by_name); g_free (mime_type); return filter_caja_handler (result); } gboolean caja_mime_has_any_applications_for_file (CajaFile *file) { GList *apps; char *mime_type; gboolean result; char *uri_scheme; GAppInfo *uri_handler; mime_type = caja_file_get_mime_type (file); apps = g_app_info_get_all_for_type (mime_type); uri_scheme = caja_file_get_uri_scheme (file); if (uri_scheme != NULL) { uri_handler = g_app_info_get_default_for_uri_scheme (uri_scheme); if (uri_handler) { apps = g_list_prepend (apps, uri_handler); } g_free (uri_scheme); } if (!file_has_local_path (file)) { /* Filter out non-uri supporting apps */ apps = filter_non_uri_apps (apps); } apps = filter_caja_handler (apps); if (apps) { result = TRUE; g_list_free_full (apps, g_object_unref); } else { result = FALSE; } g_free (mime_type); return result; } GAppInfo * caja_mime_get_default_application_for_files (GList *files) { GList *l, *sorted_files; CajaFile *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 = caja_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; } /* returns an intersection of two mime application lists, * and returns a new list, freeing a, b and all applications * that are not in the intersection set. * The lists are assumed to be pre-sorted by their IDs */ static GList * intersect_application_lists (GList *a, GList *b) { GList *l, *m; GList *ret; GAppInfo *a_app, *b_app; int cmp; ret = NULL; l = a; m = b; while (l != NULL && m != NULL) { a_app = (GAppInfo *) l->data; b_app = (GAppInfo *) m->data; cmp = application_compare_by_id (a_app, b_app); if (cmp > 0) { g_object_unref (b_app); m = m->next; } else if (cmp < 0) { g_object_unref (a_app); l = l->next; } else { g_object_unref (b_app); ret = g_list_prepend (ret, a_app); l = l->next; m = m->next; } } g_list_foreach (l, (GFunc) g_object_unref, NULL); g_list_foreach (m, (GFunc) g_object_unref, NULL); g_list_free (a); g_list_free (b); return g_list_reverse (ret); } GList * caja_mime_get_applications_for_files (GList *files) { GList *l, *sorted_files; CajaFile *file; GList *one_ret, *ret; g_assert (files != NULL); sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type); ret = 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_ret = caja_mime_get_applications_for_file (file); one_ret = g_list_sort (one_ret, (GCompareFunc) application_compare_by_id); if (ret != NULL) { ret = intersect_application_lists (ret, one_ret); } else { ret = one_ret; } if (ret == NULL) { break; } } g_list_free (sorted_files); ret = g_list_sort (ret, (GCompareFunc) application_compare_by_name); return ret; } gboolean caja_mime_has_any_applications_for_files (GList *files) { GList *l, *sorted_files; CajaFile *file; gboolean ret; g_assert (files != NULL); sorted_files = g_list_sort (g_list_copy (files), (GCompareFunc) file_compare_by_mime_type); ret = TRUE; for (l = sorted_files; l != NULL; l = l->next) { file = CAJA_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; } if (!caja_mime_has_any_applications_for_file (file)) { ret = FALSE; break; } } g_list_free (sorted_files); return ret; } 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, caja_file_get_location ((CajaFile *) node->data)); } locations = g_list_reverse (locations); caja_file_operations_trash_or_delete (locations, parent_window, NULL, NULL); g_list_free_full (locations, g_object_unref); } static void report_broken_symbolic_link (GtkWindow *parent_window, CajaFile *file) { char *target_path; char *display_name; char *prompt; char *detail; GtkDialog *dialog; GList file_as_list; int response; g_assert (caja_file_is_broken_symbolic_link (file)); display_name = caja_file_get_display_name (file); if (caja_file_is_in_trash (file)) { prompt = g_strdup_printf (_("The Link \"%s\" is Broken."), display_name); } else { prompt = g_strdup_printf (_("The Link \"%s\" is Broken. Move it to Trash?"), display_name); } g_free (display_name); target_path = caja_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 (caja_file_is_in_trash (file)) { eel_run_simple_dialog (GTK_WIDGET (parent_window), FALSE, GTK_MESSAGE_WARNING, prompt, detail, GTK_STOCK_CANCEL, NULL); goto out; } dialog = eel_show_yes_no_dialog (prompt, detail, _("Mo_ve to Trash"), GTK_STOCK_CANCEL, parent_window); gtk_dialog_set_default_response (dialog, GTK_RESPONSE_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 MateComponent 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 MateComponentUIHandler is probably going * to change wildly, I don't want to mess with this now. */ response = gtk_dialog_run (dialog); gtk_widget_destroy (GTK_WIDGET (dialog)); if (response == GTK_RESPONSE_YES) { file_as_list.data = file; file_as_list.next = NULL; file_as_list.prev = NULL; trash_or_delete_files (parent_window, &file_as_list, TRUE); } out: g_free (prompt); g_free (target_path); g_free (detail); } static ActivationAction get_executable_text_file_action (GtkWindow *parent_window, CajaFile *file) { GtkDialog *dialog; char *file_name; char *prompt; char *detail; int preferences_value; int response; g_assert (caja_file_contains_text (file)); preferences_value = g_settings_get_enum (caja_preferences, CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION); switch (preferences_value) { case CAJA_EXECUTABLE_TEXT_LAUNCH: return ACTIVATION_ACTION_LAUNCH; case CAJA_EXECUTABLE_TEXT_DISPLAY: return ACTIVATION_ACTION_OPEN_IN_APPLICATION; case CAJA_EXECUTABLE_TEXT_ASK: break; default: /* Complain non-fatally, since preference data can't be trusted */ g_warning ("Unknown value %d for CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION", preferences_value); } file_name = caja_file_get_display_name (file); prompt = g_strdup_printf (_("Do you want to run \"%s\", or display its contents?"), file_name); detail = g_strdup_printf (_("\"%s\" is an executable text file."), file_name); g_free (file_name); dialog = eel_create_question_dialog (prompt, detail, _("Run in _Terminal"), RESPONSE_RUN_IN_TERMINAL, _("_Display"), RESPONSE_DISPLAY, parent_window); gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); gtk_dialog_add_button (dialog, _("_Run"), RESPONSE_RUN); gtk_dialog_set_default_response (dialog, GTK_RESPONSE_CANCEL); gtk_widget_show (GTK_WIDGET (dialog)); g_free (prompt); g_free (detail); response = gtk_dialog_run (dialog); gtk_widget_destroy (GTK_WIDGET (dialog)); switch (response) { case RESPONSE_RUN: return ACTIVATION_ACTION_LAUNCH; case RESPONSE_RUN_IN_TERMINAL: return ACTIVATION_ACTION_LAUNCH_IN_TERMINAL; case RESPONSE_DISPLAY: return ACTIVATION_ACTION_OPEN_IN_APPLICATION; default: return ACTIVATION_ACTION_DO_NOTHING; } } static ActivationAction get_default_executable_text_file_action (void) { int preferences_value; preferences_value = g_settings_get_enum (caja_preferences, CAJA_PREFERENCES_EXECUTABLE_TEXT_ACTIVATION); switch (preferences_value) { case CAJA_EXECUTABLE_TEXT_LAUNCH: return ACTIVATION_ACTION_LAUNCH; case CAJA_EXECUTABLE_TEXT_DISPLAY: return ACTIVATION_ACTION_OPEN_IN_APPLICATION; case CAJA_EXECUTABLE_TEXT_ASK: default: return ACTIVATION_ACTION_ASK; } } gboolean caja_mime_file_opens_in_view (CajaFile *file) { return (caja_file_is_directory (file) || CAJA_IS_DESKTOP_ICON_FILE (file) || caja_file_is_caja_link (file)); } static ActivationAction get_activation_action (CajaFile *file) { ActivationAction action; char *activation_uri; if (caja_file_is_launcher (file)) { return ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE; } activation_uri = caja_file_get_activation_uri (file); if (activation_uri == NULL) { activation_uri = caja_file_get_uri (file); } action = ACTIVATION_ACTION_DO_NOTHING; if (caja_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 (caja_file_contains_text (file)) { action = get_default_executable_text_file_action (); } g_free (executable_path); } if (action == ACTIVATION_ACTION_DO_NOTHING) { if (caja_mime_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 caja_mime_file_opens_in_external_app (CajaFile *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 CajaFiles, * 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; CajaFile *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 = caja_file_get_by_uri (uri); app = caja_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); } caja_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 (CajaFile *file) { GError *error; error = caja_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 (CajaFile *file) { GError *error; error = caja_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_info) { g_object_remove_weak_pointer (G_OBJECT (parameters->slot_info), (gpointer *)¶meters->slot_info); } 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); caja_file_list_free (parameters->mountables); caja_file_list_free (parameters->start_mountables); caja_file_list_free (parameters->not_mounted); g_free (parameters->activation_directory); g_free (parameters->timed_wait_prompt); g_assert (parameters->files_handle == NULL); 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) { caja_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 gboolean confirm_multiple_windows (GtkWindow *parent_window, int count, gboolean use_tabs) { GtkDialog *dialog; char *prompt; char *detail; int response; if (count <= SILENT_WINDOW_OPEN_LIMIT) { return TRUE; } prompt = _("Are you sure you want to open all files?"); if (use_tabs) { detail = g_strdup_printf (ngettext("This will open %d separate tab.", "This will open %d separate tabs.", count), count); } else { detail = g_strdup_printf (ngettext("This will open %d separate window.", "This will open %d separate windows.", count), count); } dialog = eel_show_yes_no_dialog (prompt, detail, GTK_STOCK_OK, GTK_STOCK_CANCEL, parent_window); g_free (detail); response = gtk_dialog_run (dialog); gtk_widget_destroy (GTK_WIDGET (dialog)); return response == GTK_RESPONSE_YES; } typedef struct { CajaWindowSlotInfo *slot_info; GtkWindow *parent_window; CajaFile *file; GList *files; CajaWindowOpenMode mode; CajaWindowOpenFlags 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_info) { g_object_remove_weak_pointer (G_OBJECT (parameters_install->slot_info), (gpointer *)¶meters_install->slot_info); } 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); } caja_file_unref (parameters_install->file); caja_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 (CajaFile *file, char *uri) { char *uri_for_display; char *nice_uri; char *error_message; GFile *location; /* For local files, we want to use filename if possible */ if (caja_file_is_local (file)) { location = caja_file_get_location (file); nice_uri = g_file_get_parse_name (location); g_object_unref (location); } else { nice_uri = g_strdup (uri); } /* 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 (nice_uri, MAX_URI_IN_DIALOG_LENGTH); error_message = g_strdup_printf (_("Could not display \"%s\"."), uri_for_display); g_free (nice_uri); g_free (uri_for_display); return error_message; } static void application_selected_cb (CajaOpenWithDialog *dialog, GAppInfo *app, gpointer user_data) { GtkWindow *parent_window; CajaFile *file; GList files; parent_window = GTK_WINDOW (user_data); file = g_object_get_data (G_OBJECT (dialog), "mime-action:file"); files.next = NULL; files.prev = NULL; files.data = file; caja_launch_application (app, &files, parent_window); } static void choose_program (GtkDialog *message_dialog, int response, gpointer callback_data) { GtkWidget *dialog; char *uri; char *mime_type; CajaFile *file; if (response != GTK_RESPONSE_ACCEPT) { gtk_widget_destroy (GTK_WIDGET (message_dialog)); return; } file = g_object_get_data (G_OBJECT (message_dialog), "mime-action:file"); g_assert (CAJA_IS_FILE (file)); caja_file_ref (file); uri = caja_file_get_uri (file); mime_type = caja_file_get_mime_type (file); dialog = caja_open_with_dialog_new (uri, mime_type, NULL); g_object_set_data_full (G_OBJECT (dialog), "mime-action:file", caja_file_ref (file), (GDestroyNotify)caja_file_unref); gtk_window_set_screen (GTK_WINDOW (dialog), gtk_widget_get_screen (GTK_WIDGET (callback_data))); /* Destroy the message dialog after ref:ing the file */ gtk_widget_destroy (GTK_WIDGET (message_dialog)); gtk_widget_show (dialog); g_signal_connect_object (dialog, "application_selected", G_CALLBACK (application_selected_cb), callback_data, 0); g_free (uri); g_free (mime_type); caja_file_unref (file); } static void show_unhandled_type_error (ActivateParametersInstall *parameters) { GtkWidget *dialog; char *mime_type = caja_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)) { dialog = gtk_message_dialog_new (parameters->parent_window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, 0, NULL); g_object_set (dialog, "text", error_message, "secondary-text", _("The file is of an unknown type"), NULL); } else { char *text; text = g_strdup_printf (_("There is no application installed for %s files"), g_content_type_get_description (mime_type)); dialog = gtk_message_dialog_new (parameters->parent_window, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, 0, NULL); g_object_set (dialog, "text", error_message, "secondary-text", text, NULL); g_free (text); } gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Select Application"), GTK_RESPONSE_ACCEPT); gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_OK, GTK_RESPONSE_OK); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); g_object_set_data_full (G_OBJECT (dialog), "mime-action:file", caja_file_ref (parameters->file), (GDestroyNotify)caja_file_unref); gtk_widget_show (GTK_WIDGET (dialog)); g_signal_connect (dialog, "response", G_CALLBACK (choose_program), parameters->parent_window); 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); eel_show_error_dialog (_("Unable to search for application"), message, parameters_install->parent_window); g_free (message); } g_error_free (error); activate_parameters_install_free (parameters_install); return; } g_variant_unref (variant); /* activate the file again */ caja_mime_activate_files (parameters_install->parent_window, parameters_install->slot_info, parameters_install->files, parameters_install->activation_directory, parameters_install->mode, parameters_install->flags, parameters_install->user_confirmation); activate_parameters_install_free (parameters_install); } static void search_for_application_mime_type (ActivateParametersInstall *parameters_install, const gchar *mime_type) { GdkWindow *window; guint xid = 0; const char *mime_types[2]; g_assert (parameters_install->proxy != NULL); /* get XID from parent window */ window = gtk_widget_get_window (GTK_WIDGET (parameters_install->parent_window)); if (window != NULL) { xid = GDK_WINDOW_XID (window); } mime_types[0] = mime_type; mime_types[1] = NULL; g_dbus_proxy_call (parameters_install->proxy, "InstallMimeTypes", g_variant_new ("(u^ass)", xid, mime_types, "hide-confirm-search"), G_DBUS_CALL_FLAGS_NONE, G_MAXINT /* no timeout */, NULL /* cancellable */, (GAsyncReadyCallback) search_for_application_dbus_call_notify_cb, parameters_install); caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, "InstallMimeType method invoked for %s", mime_type); } static void application_unhandled_file_install (GtkDialog *dialog, gint response_id, ActivateParametersInstall *parameters_install) { char *mime_type; gtk_widget_destroy (GTK_WIDGET (dialog)); parameters_install->dialog = NULL; if (response_id == GTK_RESPONSE_YES) { mime_type = caja_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 gboolean delete_cb (GtkDialog *dialog) { gtk_dialog_response (dialog, GTK_RESPONSE_DELETE_EVENT); return TRUE; } static void pk_proxy_appeared_cb (GObject *source, GAsyncResult *res, gpointer user_data) { ActivateParametersInstall *parameters_install = user_data; char *mime_type; char *error_message; GtkWidget *dialog; GDBusProxy *proxy; GError *error = NULL; proxy = g_dbus_proxy_new_for_bus_finish (res, &error); if (error != NULL) { g_warning ("Couldn't call Modify on the PackageKit interface: %s", error->message); g_error_free (error); /* show an unhelpful dialog */ show_unhandled_type_error (parameters_install); /* The callback wasn't started, so we have to free the parameters */ activate_parameters_install_free (parameters_install); return; } mime_type = caja_file_get_mime_type (parameters_install->file); 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 = gtk_message_dialog_new (parameters_install->parent_window, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_YES_NO, "%s", error_message); gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("There is no application installed for %s files.\n" "Do you want to search for an application to open this file?"), g_content_type_get_description (mime_type)); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); parameters_install->dialog = dialog; parameters_install->proxy = proxy; g_signal_connect (dialog, "response", G_CALLBACK (application_unhandled_file_install), parameters_install); g_signal_connect (dialog, "delete-event", G_CALLBACK (delete_cb), NULL); gtk_widget_show_all (dialog); g_free (mime_type); } static void application_unhandled_uri (ActivateParameters *parameters, char *uri) { gboolean show_install_mime; char *mime_type; CajaFile *file; ActivateParametersInstall *parameters_install; file = caja_file_get_by_uri (uri); mime_type = caja_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_info = parameters->slot_info; g_object_add_weak_pointer (G_OBJECT (parameters_install->slot_info), (gpointer *)¶meters_install->slot_info); 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->mode = parameters->mode; 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 (caja_preferences, CAJA_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; goto out; } 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.Modify", NULL, pk_proxy_appeared_cb, parameters_install); return; out: /* show an unhelpful dialog */ show_unhandled_type_error (parameters_install); /* The callback wasn't started, so we have to free the parameters */ activate_parameters_install_free (parameters_install); g_free (mime_type); } typedef struct { GtkWindow *parent_window; CajaFile *file; } ActivateParametersDesktop; static void activate_parameters_desktop_free (ActivateParametersDesktop *parameters_desktop) { if (parameters_desktop->parent_window) { g_object_remove_weak_pointer (G_OBJECT (parameters_desktop->parent_window), (gpointer *)¶meters_desktop->parent_window); } caja_file_unref (parameters_desktop->file); g_free (parameters_desktop); } static void untrusted_launcher_response_callback (GtkDialog *dialog, int response_id, ActivateParametersDesktop *parameters) { GdkScreen *screen; char *uri; GFile *file; switch (response_id) { case RESPONSE_RUN: screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window)); uri = caja_file_get_uri (parameters->file); caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, "directory view activate_callback launch_desktop_file window=%p: %s", parameters->parent_window, uri); caja_launch_desktop_file (screen, uri, NULL, parameters->parent_window); g_free (uri); break; case RESPONSE_MARK_TRUSTED: file = caja_file_get_location (parameters->file); caja_file_mark_desktop_file_trusted (file, parameters->parent_window, TRUE, NULL, NULL); g_object_unref (file); break; default: /* Just destroy dialog */ break; } gtk_widget_destroy (GTK_WIDGET (dialog)); activate_parameters_desktop_free (parameters); } static void activate_desktop_file (ActivateParameters *parameters, CajaFile *file) { ActivateParametersDesktop *parameters_desktop; char *primary, *secondary, *display_name; GtkWidget *dialog; GdkScreen *screen; char *uri; screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window)); if (!caja_file_is_trusted_link (file)) { /* copy the parts of parameters we are interested in as the orignal will be freed */ parameters_desktop = g_new0 (ActivateParametersDesktop, 1); if (parameters->parent_window) { parameters_desktop->parent_window = parameters->parent_window; g_object_add_weak_pointer (G_OBJECT (parameters_desktop->parent_window), (gpointer *)¶meters_desktop->parent_window); } parameters_desktop->file = caja_file_ref (file); primary = _("Untrusted application launcher"); display_name = caja_file_get_display_name (file); secondary = g_strdup_printf (_("The application launcher \"%s\" has not been marked as trusted. " "If you do not know the source of this file, launching it may be unsafe." ), display_name); dialog = gtk_message_dialog_new (parameters->parent_window, 0, GTK_MESSAGE_WARNING, GTK_BUTTONS_NONE, NULL); g_object_set (dialog, "text", primary, "secondary-text", secondary, NULL); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Launch Anyway"), RESPONSE_RUN); if (caja_file_can_set_permissions (file)) { gtk_dialog_add_button (GTK_DIALOG (dialog), _("Mark as _Trusted"), RESPONSE_MARK_TRUSTED); } gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); g_signal_connect (dialog, "response", G_CALLBACK (untrusted_launcher_response_callback), parameters_desktop); gtk_widget_show (dialog); g_free (display_name); g_free (secondary); return; } uri = caja_file_get_uri (file); caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, "directory view activate_callback launch_desktop_file window=%p: %s", parameters->parent_window, uri); caja_launch_desktop_file (screen, uri, NULL, parameters->parent_window); g_free (uri); } static void activate_files (ActivateParameters *parameters) { CajaWindowInfo *window_info; CajaWindowOpenFlags flags; CajaFile *file; GList *launch_desktop_files; GList *launch_files; GList *launch_in_terminal_files; GList *open_in_app_uris; GList *open_in_app_parameters; GList *unhandled_open_in_app_uris; ApplicationLaunchParameters *one_parameters; GList *open_in_view_files; GList *l; int count; char *uri; char *executable_path, *quoted_path, *name; char *old_working_dir; ActivationAction action; GdkScreen *screen; LaunchLocation *location; screen = gtk_widget_get_screen (GTK_WIDGET (parameters->parent_window)); launch_desktop_files = NULL; launch_files = NULL; launch_in_terminal_files = NULL; open_in_app_uris = NULL; open_in_view_files = NULL; for (l = parameters->locations; l != NULL; l = l->next) { location = l->data; file = location->file; if (file_was_cancelled (file)) { continue; } action = get_activation_action (file); if (action == ACTIVATION_ACTION_ASK) { /* Special case for executable text files, since it might be * dangerous & unexpected to launch these. */ pause_activation_timed_cancel (parameters); action = get_executable_text_file_action (parameters->parent_window, file); unpause_activation_timed_cancel (parameters); } switch (action) { case ACTIVATION_ACTION_LAUNCH_DESKTOP_FILE : launch_desktop_files = g_list_prepend (launch_desktop_files, file); break; case ACTIVATION_ACTION_LAUNCH : launch_files = g_list_prepend (launch_files, file); break; case ACTIVATION_ACTION_LAUNCH_IN_TERMINAL : launch_in_terminal_files = g_list_prepend (launch_in_terminal_files, file); break; case ACTIVATION_ACTION_OPEN_IN_VIEW : open_in_view_files = g_list_prepend (open_in_view_files, file); break; case ACTIVATION_ACTION_OPEN_IN_APPLICATION : open_in_app_uris = g_list_prepend (open_in_app_uris, location->uri); break; case ACTIVATION_ACTION_DO_NOTHING : break; case ACTIVATION_ACTION_ASK : g_assert_not_reached (); break; } } launch_desktop_files = g_list_reverse (launch_desktop_files); for (l = launch_desktop_files; l != NULL; l = l->next) { file = CAJA_FILE (l->data); activate_desktop_file (parameters, file); } old_working_dir = NULL; if (parameters->activation_directory && (launch_files != NULL || launch_in_terminal_files != NULL)) { old_working_dir = g_get_current_dir (); g_chdir (parameters->activation_directory); } launch_files = g_list_reverse (launch_files); for (l = launch_files; l != NULL; l = l->next) { file = CAJA_FILE (l->data); uri = caja_file_get_activation_uri (file); executable_path = g_filename_from_uri (uri, NULL, NULL); quoted_path = g_shell_quote (executable_path); name = caja_file_get_name (file); caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, "directory view activate_callback launch_file window=%p: %s", parameters->parent_window, quoted_path); caja_launch_application_from_command (screen, name, quoted_path, FALSE, NULL); g_free (name); g_free (quoted_path); g_free (executable_path); g_free (uri); } launch_in_terminal_files = g_list_reverse (launch_in_terminal_files); for (l = launch_in_terminal_files; l != NULL; l = l->next) { file = CAJA_FILE (l->data); uri = caja_file_get_activation_uri (file); executable_path = g_filename_from_uri (uri, NULL, NULL); quoted_path = g_shell_quote (executable_path); name = caja_file_get_name (file); caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, "directory view activate_callback launch_in_terminal window=%p: %s", parameters->parent_window, quoted_path); caja_launch_application_from_command (screen, name, quoted_path, TRUE, NULL); g_free (name); g_free (quoted_path); g_free (executable_path); g_free (uri); } if (old_working_dir != NULL) { g_chdir (old_working_dir); g_free (old_working_dir); } open_in_view_files = g_list_reverse (open_in_view_files); count = g_list_length (open_in_view_files); flags = parameters->flags; if (count > 1) { if ((parameters->flags & CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW) == 0) { flags |= CAJA_WINDOW_OPEN_FLAG_NEW_TAB; } else { flags |= CAJA_WINDOW_OPEN_FLAG_NEW_WINDOW; } } if (parameters->slot_info != NULL && (!parameters->user_confirmation || confirm_multiple_windows (parameters->parent_window, count, (flags & CAJA_WINDOW_OPEN_FLAG_NEW_TAB) != 0))) { if ((flags & CAJA_WINDOW_OPEN_FLAG_NEW_TAB) != 0 && g_settings_get_enum (caja_preferences, CAJA_PREFERENCES_NEW_TAB_POSITION) == CAJA_NEW_TAB_POSITION_AFTER_CURRENT_TAB) { /* 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. */ open_in_view_files = g_list_reverse (open_in_view_files); } for (l = open_in_view_files; l != NULL; l = l->next) { GFile *f; /* The ui should ask for navigation or object windows * depending on what the current one is */ file = CAJA_FILE (l->data); uri = caja_file_get_activation_uri (file); f = g_file_new_for_uri (uri); caja_window_slot_info_open_location (parameters->slot_info, f, parameters->mode, flags, NULL); g_object_unref (f); g_free (uri); } } open_in_app_parameters = NULL; unhandled_open_in_app_uris = NULL; if (open_in_app_uris != NULL) { open_in_app_uris = g_list_reverse (open_in_app_uris); open_in_app_parameters = make_activation_parameters (open_in_app_uris, &unhandled_open_in_app_uris); } for (l = open_in_app_parameters; l != NULL; l = l->next) { one_parameters = l->data; caja_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) { uri = l->data; /* this does not block */ application_unhandled_uri (parameters, uri); } window_info = NULL; if (parameters->slot_info != NULL) { window_info = caja_window_slot_info_get_window (parameters->slot_info); } if (open_in_app_parameters != NULL || unhandled_open_in_app_uris != NULL) { if ((parameters->flags & CAJA_WINDOW_OPEN_FLAG_CLOSE_BEHIND) != 0 && window_info != NULL && caja_window_info_get_window_type (window_info) == CAJA_WINDOW_SPATIAL) { caja_window_info_close (window_info); } } g_list_free (launch_desktop_files); g_list_free (launch_files); g_list_free (launch_in_terminal_files); g_list_free (open_in_view_files); g_list_free (open_in_app_uris); g_list_free (open_in_app_parameters); g_list_free (unhandled_open_in_app_uris); 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; CajaFile *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)) { eel_show_error_dialog (_("Unable to mount location"), error->message, parameters->parent_window); } 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); caja_file_unref (file); activation_mount_not_mounted (parameters); } static void activation_mount_not_mounted (ActivateParameters *parameters) { CajaFile *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 = caja_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; caja_file_invalidate_all_attributes (loc->file); } files = get_file_list_for_launch_locations (parameters->locations); caja_file_list_call_when_ready (files, caja_mime_actions_get_required_file_attributes () | CAJA_FILE_ATTRIBUTE_LINK_INFO, ¶meters->files_handle, activate_callback, parameters); caja_file_list_free (files); } static void activate_callback (GList *files, gpointer callback_data) { ActivateParameters *parameters = callback_data; GList *l, *next; CajaFile *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, caja_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; CajaFile *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 (caja_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 (caja_file_get_file_type (file) == G_FILE_TYPE_MOUNTABLE && !caja_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 = caja_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); caja_file_list_call_when_ready (files, caja_mime_actions_get_required_file_attributes () | CAJA_FILE_ATTRIBUTE_LINK_INFO, ¶meters->files_handle, activate_callback, parameters); caja_file_list_free (files); } static void activation_get_activation_uris (ActivateParameters *parameters) { GList *l, *files; CajaFile *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 (caja_file_is_symbolic_link (file)) { caja_file_invalidate_attributes (file, CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO); } } if (parameters->locations == NULL) { activation_parameters_free (parameters); return; } files = get_file_list_for_launch_locations (parameters->locations); caja_file_list_call_when_ready (files, CAJA_FILE_ATTRIBUTE_INFO | CAJA_FILE_ATTRIBUTE_LINK_INFO, ¶meters->files_handle, activate_activation_uris_ready_callback, parameters); caja_file_list_free (files); } static void activation_mountable_mounted (CajaFile *file, GFile *result_location, GError *error, gpointer callback_data) { ActivateParameters *parameters = callback_data; CajaFile *target_file; LaunchLocation *location; /* Remove from list of files that have to be mounted */ parameters->mountables = g_list_remove (parameters->mountables, file); caja_file_unref (file); if (error == NULL) { /* Replace file with the result of the mount */ target_file = caja_file_get (result_location); location = find_launch_location_for_file (parameters->locations, file); if (location) { launch_location_update_from_file (location, target_file); } caja_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)) { eel_show_error_dialog (_("Unable to mount location"), error->message, parameters->parent_window); } 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) { CajaFile *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); caja_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) activation_get_activation_uris (parameters); } static void activation_mountable_started (CajaFile *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); caja_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)) { eel_show_error_dialog (_("Unable to start location"), error->message, NULL); } 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) { CajaFile *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); caja_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) activation_get_activation_uris (parameters); } /** * caja_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 CajaFiles to activate. * **/ void caja_mime_activate_files (GtkWindow *parent_window, CajaWindowSlotInfo *slot_info, GList *files, const char *launch_directory, CajaWindowOpenMode mode, CajaWindowOpenFlags flags, gboolean user_confirmation) { ActivateParameters *parameters; char *file_name; int file_count; GList *l, *next; CajaFile *file; LaunchLocation *location; if (files == NULL) { return; } caja_debug_log_with_file_list (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, files, "caja_mime_activate_files window=%p", parent_window); parameters = g_new0 (ActivateParameters, 1); parameters->slot_info = slot_info; g_object_add_weak_pointer (G_OBJECT (parameters->slot_info), (gpointer *)¶meters->slot_info); 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->mode = mode; parameters->flags = flags; parameters->user_confirmation = user_confirmation; file_count = g_list_length (files); if (file_count == 1) { file_name = caja_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 (caja_file_can_mount (file)) { parameters->mountables = g_list_prepend (parameters->mountables, caja_file_ref (file)); } if (caja_file_can_start (file)) { parameters->start_mountables = g_list_prepend (parameters->start_mountables, caja_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) activation_get_activation_uris (parameters); } /** * caja_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 CajaFile representing the file in this view to activate. * @use_new_window: Should this item be opened in a new window? * **/ void caja_mime_activate_file (GtkWindow *parent_window, CajaWindowSlotInfo *slot_info, CajaFile *file, const char *launch_directory, CajaWindowOpenMode mode, CajaWindowOpenFlags flags) { GList *files; g_return_if_fail (CAJA_IS_FILE (file)); files = g_list_prepend (NULL, file); caja_mime_activate_files (parent_window, slot_info, files, launch_directory, mode, flags, FALSE); g_list_free (files); }