diff options
Diffstat (limited to 'libcaja-private/caja-mime-actions.c')
-rw-r--r-- | libcaja-private/caja-mime-actions.c | 2597 |
1 files changed, 2597 insertions, 0 deletions
diff --git a/libcaja-private/caja-mime-actions.c b/libcaja-private/caja-mime-actions.c new file mode 100644 index 00000000..c946e938 --- /dev/null +++ b/libcaja-private/caja-mime-actions.c @@ -0,0 +1,2597 @@ +/* -*- 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Maciej Stachowiak <[email protected]> +*/ + +#include <config.h> +#include "caja-mime-actions.h" + +#include <eel/eel-glib-extensions.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-alert-dialog.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" + +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); + eel_g_list_free_deep (parameters->uris); + + 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; + eel_g_object_list_free (apps); + } + 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); + eel_g_object_list_free (locations); +} + +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_object_destroy (GTK_OBJECT (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 = eel_preferences_get_enum + (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_object_destroy (GTK_OBJECT (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 = eel_preferences_get_enum + (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_object_destroy (GTK_OBJECT (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 = eel_alert_dialog_new (parameters->parent_window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + 0, + error_message, + _("The file is of an unknown type")); + } + else + { + char *text; + text = g_strdup_printf (_("There is no application installed for %s files"), g_content_type_get_description (mime_type)); + + dialog = eel_alert_dialog_new (parameters->parent_window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + 0, + error_message, + text); + + 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 = eel_preferences_get_boolean (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 = eel_alert_dialog_new (parameters->parent_window, + 0, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + primary, + secondary); + 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 && + eel_preferences_get_enum (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); +} |