diff options
Diffstat (limited to 'libcaja-private/caja-autorun.c')
-rw-r--r-- | libcaja-private/caja-autorun.c | 1434 |
1 files changed, 1434 insertions, 0 deletions
diff --git a/libcaja-private/caja-autorun.c b/libcaja-private/caja-autorun.c new file mode 100644 index 00000000..eacef167 --- /dev/null +++ b/libcaja-private/caja-autorun.c @@ -0,0 +1,1434 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Caja + * + * Copyright (C) 2008 Red Hat, Inc. + * + * Caja is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: David Zeuthen <[email protected]> + */ + +#include <config.h> +#include <string.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <gio/gdesktopappinfo.h> +#include <X11/XKBlib.h> +#include <gdk/gdkkeysyms.h> + +#include <eel/eel-glib-extensions.h> +#include <eel/eel-stock-dialogs.h> + +#include "caja-icon-info.h" +#include "caja-global-preferences.h" +#include "caja-file-operations.h" +#include "caja-autorun.h" +#include "caja-program-choosing.h" +#include "caja-open-with-dialog.h" +#include "caja-desktop-icon-file.h" +#include "caja-file-utilities.h" + +enum +{ + AUTORUN_ASK, + AUTORUN_IGNORE, + AUTORUN_APP, + AUTORUN_OPEN_FOLDER, + AUTORUN_SEP, + AUTORUN_OTHER_APP, +}; +enum +{ + COLUMN_AUTORUN_PIXBUF, + COLUMN_AUTORUN_NAME, + COLUMN_AUTORUN_APP_INFO, + COLUMN_AUTORUN_X_CONTENT_TYPE, + COLUMN_AUTORUN_ITEM_TYPE, +}; + +static gboolean should_autorun_mount (GMount *mount); + +static void caja_autorun_rebuild_combo_box (GtkWidget *combo_box); + +void +caja_autorun_get_preferences (const char *x_content_type, + gboolean *pref_start_app, + gboolean *pref_ignore, + gboolean *pref_open_folder) +{ + char **x_content_start_app; + char **x_content_ignore; + char **x_content_open_folder; + + g_return_if_fail (pref_start_app != NULL); + g_return_if_fail (pref_ignore != NULL); + g_return_if_fail (pref_open_folder != NULL); + + *pref_start_app = FALSE; + *pref_ignore = FALSE; + *pref_open_folder = FALSE; + x_content_start_app = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP); + x_content_ignore = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE); + x_content_open_folder = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER); + if (x_content_start_app != NULL) + { + *pref_start_app = eel_g_strv_find (x_content_start_app, x_content_type) != -1; + } + if (x_content_ignore != NULL) + { + *pref_ignore = eel_g_strv_find (x_content_ignore, x_content_type) != -1; + } + if (x_content_open_folder != NULL) + { + *pref_open_folder = eel_g_strv_find (x_content_open_folder, x_content_type) != -1; + } + g_strfreev (x_content_ignore); + g_strfreev (x_content_start_app); + g_strfreev (x_content_open_folder); +} + +static void +remove_elem_from_str_array (char **v, const char *s) +{ + int n, m; + + if (v == NULL) + { + return; + } + + for (n = 0; v[n] != NULL; n++) + { + if (strcmp (v[n], s) == 0) + { + for (m = n + 1; v[m] != NULL; m++) + { + v[m - 1] = v[m]; + } + v[m - 1] = NULL; + n--; + } + } +} + +static char ** +add_elem_to_str_array (char **v, const char *s) +{ + guint len; + char **r; + + len = v != NULL ? g_strv_length (v) : 0; + r = g_new0 (char *, len + 2); + memcpy (r, v, len * sizeof (char *)); + r[len] = g_strdup (s); + r[len+1] = NULL; + g_free (v); + + return r; +} + + +void +caja_autorun_set_preferences (const char *x_content_type, + gboolean pref_start_app, + gboolean pref_ignore, + gboolean pref_open_folder) +{ + char **x_content_start_app; + char **x_content_ignore; + char **x_content_open_folder; + + g_assert (x_content_type != NULL); + + x_content_start_app = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP); + x_content_ignore = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE); + x_content_open_folder = eel_preferences_get_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER); + + remove_elem_from_str_array (x_content_start_app, x_content_type); + if (pref_start_app) + { + x_content_start_app = add_elem_to_str_array (x_content_start_app, x_content_type); + } + eel_preferences_set_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_START_APP, x_content_start_app); + + remove_elem_from_str_array (x_content_ignore, x_content_type); + if (pref_ignore) + { + x_content_ignore = add_elem_to_str_array (x_content_ignore, x_content_type); + } + eel_preferences_set_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_IGNORE, x_content_ignore); + + remove_elem_from_str_array (x_content_open_folder, x_content_type); + if (pref_open_folder) + { + x_content_open_folder = add_elem_to_str_array (x_content_open_folder, x_content_type); + } + eel_preferences_set_string_array (CAJA_PREFERENCES_MEDIA_AUTORUN_X_CONTENT_OPEN_FOLDER, x_content_open_folder); + + g_strfreev (x_content_open_folder); + g_strfreev (x_content_ignore); + g_strfreev (x_content_start_app); + +} + +static gboolean +combo_box_separator_func (GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + char *str; + + gtk_tree_model_get (model, iter, + 1, &str, + -1); + if (str != NULL) + { + g_free (str); + return FALSE; + } + return TRUE; +} + +typedef struct +{ + guint changed_signal_id; + GtkWidget *combo_box; + + char *x_content_type; + gboolean include_ask; + gboolean include_open_with_other_app; + + gboolean update_settings; + CajaAutorunComboBoxChanged changed_cb; + gpointer user_data; + + gboolean other_application_selected; +} CajaAutorunComboBoxData; + +static void +caja_autorun_combobox_data_destroy (CajaAutorunComboBoxData *data) +{ + /* signal handler may be automatically disconnected by destroying the widget */ + if (g_signal_handler_is_connected (G_OBJECT (data->combo_box), data->changed_signal_id)) + { + g_signal_handler_disconnect (G_OBJECT (data->combo_box), data->changed_signal_id); + } + g_free (data->x_content_type); + g_free (data); +} + +static void +other_application_selected (CajaOpenWithDialog *dialog, + GAppInfo *app_info, + CajaAutorunComboBoxData *data) +{ + if (data->changed_cb != NULL) + { + data->changed_cb (TRUE, FALSE, FALSE, app_info, data->user_data); + } + if (data->update_settings) + { + caja_autorun_set_preferences (data->x_content_type, TRUE, FALSE, FALSE); + g_app_info_set_as_default_for_type (app_info, + data->x_content_type, + NULL); + data->other_application_selected = TRUE; + } + + /* rebuild so we include and select the new application in the list */ + caja_autorun_rebuild_combo_box (data->combo_box); +} + +static void +handle_dialog_closure (CajaAutorunComboBoxData *data) +{ + if (!data->other_application_selected) + { + /* reset combo box so we don't linger on "Open with other Application..." */ + caja_autorun_rebuild_combo_box (data->combo_box); + } +} + +static void +dialog_response_cb (GtkDialog *dialog, + gint response, + CajaAutorunComboBoxData *data) +{ + handle_dialog_closure (data); +} + +static void +dialog_destroy_cb (GtkObject *object, + CajaAutorunComboBoxData *data) +{ + handle_dialog_closure (data); +} + +static void +combo_box_changed (GtkComboBox *combo_box, + CajaAutorunComboBoxData *data) +{ + GtkTreeIter iter; + GtkTreeModel *model; + GAppInfo *app_info; + char *x_content_type; + int type; + + model = NULL; + app_info = NULL; + x_content_type = NULL; + + if (!gtk_combo_box_get_active_iter (combo_box, &iter)) + { + goto out; + } + + model = gtk_combo_box_get_model (combo_box); + if (model == NULL) + { + goto out; + } + + gtk_tree_model_get (model, &iter, + COLUMN_AUTORUN_APP_INFO, &app_info, + COLUMN_AUTORUN_X_CONTENT_TYPE, &x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, &type, + -1); + + switch (type) + { + case AUTORUN_ASK: + if (data->changed_cb != NULL) + { + data->changed_cb (TRUE, FALSE, FALSE, NULL, data->user_data); + } + if (data->update_settings) + { + caja_autorun_set_preferences (x_content_type, FALSE, FALSE, FALSE); + } + break; + case AUTORUN_IGNORE: + if (data->changed_cb != NULL) + { + data->changed_cb (FALSE, TRUE, FALSE, NULL, data->user_data); + } + if (data->update_settings) + { + caja_autorun_set_preferences (x_content_type, FALSE, TRUE, FALSE); + } + break; + case AUTORUN_OPEN_FOLDER: + if (data->changed_cb != NULL) + { + data->changed_cb (FALSE, FALSE, TRUE, NULL, data->user_data); + } + if (data->update_settings) + { + caja_autorun_set_preferences (x_content_type, FALSE, FALSE, TRUE); + } + break; + + case AUTORUN_APP: + if (data->changed_cb != NULL) + { + /* TODO TODO?? */ + data->changed_cb (TRUE, FALSE, FALSE, app_info, data->user_data); + } + if (data->update_settings) + { + caja_autorun_set_preferences (x_content_type, TRUE, FALSE, FALSE); + g_app_info_set_as_default_for_type (app_info, + x_content_type, + NULL); + } + break; + + case AUTORUN_OTHER_APP: + { + GtkWidget *dialog; + + data->other_application_selected = FALSE; + + dialog = caja_add_application_dialog_new (NULL, x_content_type); + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (combo_box)))); + g_signal_connect (dialog, "application_selected", + G_CALLBACK (other_application_selected), + data); + g_signal_connect (dialog, "response", + G_CALLBACK (dialog_response_cb), data); + g_signal_connect (dialog, "destroy", + G_CALLBACK (dialog_destroy_cb), data); + gtk_widget_show (GTK_WIDGET (dialog)); + + break; + } + + } + +out: + if (app_info != NULL) + { + g_object_unref (app_info); + } + g_free (x_content_type); +} + +static void +caja_autorun_rebuild_combo_box (GtkWidget *combo_box) +{ + CajaAutorunComboBoxData *data; + char *x_content_type; + + data = g_object_get_data (G_OBJECT (combo_box), "caja_autorun_combobox_data"); + if (data == NULL) + { + g_warning ("no 'caja_autorun_combobox_data' data!"); + return; + } + + x_content_type = g_strdup (data->x_content_type); + caja_autorun_prepare_combo_box (combo_box, + x_content_type, + data->include_ask, + data->include_open_with_other_app, + data->update_settings, + data->changed_cb, + data->user_data); + g_free (x_content_type); +} + +/* TODO: we need some kind of way to remove user-defined associations, + * e.g. the result of "Open with other Application...". + * + * However, this is a bit hard as + * g_app_info_can_remove_supports_type() will always return TRUE + * because we now have [Removed Applications] in the file + * ~/.local/share/applications/mimeapps.list. + * + * We need the API outlined in + * + * http://bugzilla.gnome.org/show_bug.cgi?id=545350 + * + * to do this. + * + * Now, there's also the question about what the UI would look like + * given this API. Ideally we'd include a small button on the right + * side of the combo box that the user can press to delete an + * association, e.g.: + * + * +-------------------------------------+ + * | Ask what to do | + * | Do Nothing | + * | Open Folder | + * +-------------------------------------+ + * | Open Rhythmbox Music Player | + * | Open Audio CD Extractor | + * | Open Banshee Media Player | + * | Open Frobnicator App [x] | + * +-------------------------------------+ + * | Open with other Application... | + * +-------------------------------------+ + * + * where "Frobnicator App" have been set up using "Open with other + * Application...". However this is not accessible (which is a + * GTK+ issue) but probably not a big deal. + * + * And we only want show these buttons (e.g. [x]) for associations with + * GAppInfo instances that are deletable. + */ + +void +caja_autorun_prepare_combo_box (GtkWidget *combo_box, + const char *x_content_type, + gboolean include_ask, + gboolean include_open_with_other_app, + gboolean update_settings, + CajaAutorunComboBoxChanged changed_cb, + gpointer user_data) +{ + GList *l; + GList *app_info_list; + GAppInfo *default_app_info; + GtkListStore *list_store; + GtkTreeIter iter; + GdkPixbuf *pixbuf; + int icon_size; + int set_active; + int n; + int num_apps; + gboolean pref_ask; + gboolean pref_start_app; + gboolean pref_ignore; + gboolean pref_open_folder; + CajaAutorunComboBoxData *data; + GtkCellRenderer *renderer; + gboolean new_data; + + caja_autorun_get_preferences (x_content_type, &pref_start_app, &pref_ignore, &pref_open_folder); + pref_ask = !pref_start_app && !pref_ignore && !pref_open_folder; + + icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_MENU); + + set_active = -1; + data = NULL; + new_data = TRUE; + + app_info_list = g_app_info_get_all_for_type (x_content_type); + default_app_info = g_app_info_get_default_for_type (x_content_type, FALSE); + num_apps = g_list_length (app_info_list); + + list_store = gtk_list_store_new (5, + GDK_TYPE_PIXBUF, + G_TYPE_STRING, + G_TYPE_APP_INFO, + G_TYPE_STRING, + G_TYPE_INT); + + /* no apps installed */ + if (num_apps == 0) + { + gtk_list_store_append (list_store, &iter); + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + GTK_STOCK_DIALOG_ERROR, + icon_size, + 0, + NULL); + + /* TODO: integrate with PackageKit-mate to find applications */ + + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, pixbuf, + COLUMN_AUTORUN_NAME, _("No applications found"), + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_ASK, + -1); + g_object_unref (pixbuf); + } + else + { + if (include_ask) + { + gtk_list_store_append (list_store, &iter); + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + GTK_STOCK_DIALOG_QUESTION, + icon_size, + 0, + NULL); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, pixbuf, + COLUMN_AUTORUN_NAME, _("Ask what to do"), + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_ASK, + -1); + g_object_unref (pixbuf); + } + + gtk_list_store_append (list_store, &iter); + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + GTK_STOCK_CLOSE, + icon_size, + 0, + NULL); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, pixbuf, + COLUMN_AUTORUN_NAME, _("Do Nothing"), + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_IGNORE, + -1); + g_object_unref (pixbuf); + + gtk_list_store_append (list_store, &iter); + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "folder-open", + icon_size, + 0, + NULL); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, pixbuf, + COLUMN_AUTORUN_NAME, _("Open Folder"), + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_OPEN_FOLDER, + -1); + g_object_unref (pixbuf); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, NULL, + COLUMN_AUTORUN_NAME, NULL, + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, NULL, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_SEP, + -1); + + for (l = app_info_list, n = include_ask ? 4 : 3; l != NULL; l = l->next, n++) + { + GIcon *icon; + CajaIconInfo *icon_info; + char *open_string; + GAppInfo *app_info = l->data; + + /* we deliberately ignore should_show because some apps might want + * to install special handlers that should be hidden in the regular + * application launcher menus + */ + + icon = g_app_info_get_icon (app_info); + icon_info = caja_icon_info_lookup (icon, icon_size); + pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size); + g_object_unref (icon_info); + + open_string = g_strdup_printf (_("Open %s"), g_app_info_get_display_name (app_info)); + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, pixbuf, + COLUMN_AUTORUN_NAME, open_string, + COLUMN_AUTORUN_APP_INFO, app_info, + COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_APP, + -1); + if (pixbuf != NULL) + { + g_object_unref (pixbuf); + } + g_free (open_string); + + if (g_app_info_equal (app_info, default_app_info)) + { + set_active = n; + } + } + } + + if (include_open_with_other_app) + { + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, NULL, + COLUMN_AUTORUN_NAME, NULL, + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, NULL, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_SEP, + -1); + + gtk_list_store_append (list_store, &iter); + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "application-x-executable", + icon_size, + 0, + NULL); + gtk_list_store_set (list_store, &iter, + COLUMN_AUTORUN_PIXBUF, pixbuf, + COLUMN_AUTORUN_NAME, _("Open with other Application..."), + COLUMN_AUTORUN_APP_INFO, NULL, + COLUMN_AUTORUN_X_CONTENT_TYPE, x_content_type, + COLUMN_AUTORUN_ITEM_TYPE, AUTORUN_OTHER_APP, + -1); + g_object_unref (pixbuf); + } + + if (default_app_info != NULL) + { + g_object_unref (default_app_info); + } + eel_g_object_list_free (app_info_list); + + gtk_combo_box_set_model (GTK_COMBO_BOX (combo_box), GTK_TREE_MODEL (list_store)); + g_object_unref (G_OBJECT (list_store)); + + gtk_cell_layout_clear (GTK_CELL_LAYOUT (combo_box)); + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer, + "pixbuf", COLUMN_AUTORUN_PIXBUF, + NULL); + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo_box), renderer, TRUE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo_box), renderer, + "text", COLUMN_AUTORUN_NAME, + NULL); + gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo_box), combo_box_separator_func, NULL, NULL); + + if (num_apps == 0) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0); + gtk_widget_set_sensitive (combo_box, FALSE); + } + else + { + gtk_widget_set_sensitive (combo_box, TRUE); + if (pref_ask && include_ask) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), 0); + } + else if (pref_ignore) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), include_ask ? 1 : 0); + } + else if (pref_open_folder) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), include_ask ? 2 : 1); + } + else if (set_active != -1) + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), set_active); + } + else + { + gtk_combo_box_set_active (GTK_COMBO_BOX (combo_box), include_ask ? 1 : 0); + } + + /* See if we have an old data around */ + data = g_object_get_data (G_OBJECT (combo_box), "caja_autorun_combobox_data"); + if (data) + { + new_data = FALSE; + g_free (data->x_content_type); + } + else + { + data = g_new0 (CajaAutorunComboBoxData, 1); + } + + data->x_content_type = g_strdup (x_content_type); + data->include_ask = include_ask; + data->include_open_with_other_app = include_open_with_other_app; + data->update_settings = update_settings; + data->changed_cb = changed_cb; + data->user_data = user_data; + data->combo_box = combo_box; + if (data->changed_signal_id == 0) + { + data->changed_signal_id = g_signal_connect (G_OBJECT (combo_box), + "changed", + G_CALLBACK (combo_box_changed), + data); + } + } + + if (new_data) + { + g_object_set_data_full (G_OBJECT (combo_box), + "caja_autorun_combobox_data", + data, + (GDestroyNotify) caja_autorun_combobox_data_destroy); + } +} + +static gboolean +is_shift_pressed (void) +{ + gboolean ret; + XkbStateRec state; + Bool status; + + ret = FALSE; + + gdk_error_trap_push (); + status = XkbGetState (GDK_DISPLAY (), XkbUseCoreKbd, &state); + gdk_error_trap_pop (); + + if (status == Success) + { + ret = state.mods & ShiftMask; + } + + return ret; +} + +enum +{ + AUTORUN_DIALOG_RESPONSE_EJECT = 0 +}; + +typedef struct +{ + GtkWidget *dialog; + + GMount *mount; + gboolean should_eject; + + gboolean selected_ignore; + gboolean selected_open_folder; + GAppInfo *selected_app; + + gboolean remember; + + char *x_content_type; + + CajaAutorunOpenWindow open_window_func; + gpointer user_data; +} AutorunDialogData; + + +void +caja_autorun_launch_for_mount (GMount *mount, GAppInfo *app_info) +{ + GFile *root; + CajaFile *file; + GList *files; + + root = g_mount_get_root (mount); + file = caja_file_get (root); + g_object_unref (root); + files = g_list_append (NULL, file); + caja_launch_application (app_info, + files, + NULL); /* TODO: what to set here? */ + g_object_unref (file); + g_list_free (files); +} + +static void autorun_dialog_mount_unmounted (GMount *mount, AutorunDialogData *data); + +static void +autorun_dialog_destroy (AutorunDialogData *data) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (data->mount), + G_CALLBACK (autorun_dialog_mount_unmounted), + data); + + gtk_widget_destroy (GTK_WIDGET (data->dialog)); + if (data->selected_app != NULL) + { + g_object_unref (data->selected_app); + } + g_object_unref (data->mount); + g_free (data->x_content_type); + g_free (data); +} + +static void +autorun_dialog_mount_unmounted (GMount *mount, AutorunDialogData *data) +{ + /* remove the dialog if the media is unmounted */ + autorun_dialog_destroy (data); +} + +static void +autorun_dialog_response (GtkDialog *dialog, gint response, AutorunDialogData *data) +{ + switch (response) + { + case AUTORUN_DIALOG_RESPONSE_EJECT: + caja_file_operations_unmount_mount (GTK_WINDOW (dialog), + data->mount, + data->should_eject, + FALSE); + break; + + case GTK_RESPONSE_NONE: + /* window was closed */ + break; + case GTK_RESPONSE_CANCEL: + break; + case GTK_RESPONSE_OK: + /* do the selected action */ + + if (data->remember) + { + /* make sure we don't ask again */ + caja_autorun_set_preferences (data->x_content_type, TRUE, data->selected_ignore, data->selected_open_folder); + if (!data->selected_ignore && !data->selected_open_folder && data->selected_app != NULL) + { + g_app_info_set_as_default_for_type (data->selected_app, + data->x_content_type, + NULL); + } + } + else + { + /* make sure we do ask again */ + caja_autorun_set_preferences (data->x_content_type, FALSE, FALSE, FALSE); + } + + if (!data->selected_ignore && !data->selected_open_folder && data->selected_app != NULL) + { + caja_autorun_launch_for_mount (data->mount, data->selected_app); + } + else if (!data->selected_ignore && data->selected_open_folder) + { + if (data->open_window_func != NULL) + data->open_window_func (data->mount, data->user_data); + } + break; + } + + autorun_dialog_destroy (data); +} + +static void +autorun_combo_changed (gboolean selected_ask, + gboolean selected_ignore, + gboolean selected_open_folder, + GAppInfo *selected_app, + gpointer user_data) +{ + AutorunDialogData *data = user_data; + + if (data->selected_app != NULL) + { + g_object_unref (data->selected_app); + } + data->selected_app = selected_app != NULL ? g_object_ref (selected_app) : NULL; + data->selected_ignore = selected_ignore; + data->selected_open_folder = selected_open_folder; +} + + +static void +autorun_always_toggled (GtkToggleButton *togglebutton, AutorunDialogData *data) +{ + data->remember = gtk_toggle_button_get_active (togglebutton); +} + +static gboolean +combo_box_enter_ok (GtkWidget *togglebutton, GdkEventKey *event, GtkDialog *dialog) +{ + if (event->keyval == GDK_KP_Enter || event->keyval == GDK_Return) + { + gtk_dialog_response (dialog, GTK_RESPONSE_OK); + return TRUE; + } + return FALSE; +} + +/* returns TRUE if a folder window should be opened */ +static gboolean +do_autorun_for_content_type (GMount *mount, const char *x_content_type, CajaAutorunOpenWindow open_window_func, gpointer user_data) +{ + AutorunDialogData *data; + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *combo_box; + GtkWidget *always_check_button; + GtkWidget *eject_button; + GtkWidget *image; + char *markup; + char *content_description; + char *mount_name; + GIcon *icon; + GdkPixbuf *pixbuf; + CajaIconInfo *icon_info; + int icon_size; + gboolean user_forced_dialog; + gboolean pref_ask; + gboolean pref_start_app; + gboolean pref_ignore; + gboolean pref_open_folder; + char *media_greeting; + gboolean ret; + + ret = FALSE; + mount_name = NULL; + + if (g_content_type_is_a (x_content_type, "x-content/win32-software")) + { + /* don't pop up the dialog anyway if the content type says + * windows software. + */ + goto out; + } + + user_forced_dialog = is_shift_pressed (); + + caja_autorun_get_preferences (x_content_type, &pref_start_app, &pref_ignore, &pref_open_folder); + pref_ask = !pref_start_app && !pref_ignore && !pref_open_folder; + + if (user_forced_dialog) + { + goto show_dialog; + } + + if (!pref_ask && !pref_ignore && !pref_open_folder) + { + GAppInfo *app_info; + app_info = g_app_info_get_default_for_type (x_content_type, FALSE); + if (app_info != NULL) + { + caja_autorun_launch_for_mount (mount, app_info); + } + goto out; + } + + if (pref_open_folder) + { + ret = TRUE; + goto out; + } + + if (pref_ignore) + { + goto out; + } + +show_dialog: + + mount_name = g_mount_get_name (mount); + + dialog = gtk_dialog_new (); + + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + hbox = gtk_hbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), hbox, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + + icon = g_mount_get_icon (mount); + icon_size = caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_DIALOG); + icon_info = caja_icon_info_lookup (icon, icon_size); + pixbuf = caja_icon_info_get_pixbuf_at_size (icon_info, icon_size); + g_object_unref (icon_info); + g_object_unref (icon); + image = gtk_image_new_from_pixbuf (pixbuf); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); + gtk_box_pack_start (GTK_BOX (hbox), image, TRUE, TRUE, 0); + /* also use the icon on the dialog */ + gtk_window_set_title (GTK_WINDOW (dialog), mount_name); + gtk_window_set_icon (GTK_WINDOW (dialog), pixbuf); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); + g_object_unref (pixbuf); + + vbox = gtk_vbox_new (FALSE, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + + label = gtk_label_new (NULL); + + + /* Customize greeting for well-known x-content types */ + if (strcmp (x_content_type, "x-content/audio-cdda") == 0) + { + media_greeting = _("You have just inserted an Audio CD."); + } + else if (strcmp (x_content_type, "x-content/audio-dvd") == 0) + { + media_greeting = _("You have just inserted an Audio DVD."); + } + else if (strcmp (x_content_type, "x-content/video-dvd") == 0) + { + media_greeting = _("You have just inserted a Video DVD."); + } + else if (strcmp (x_content_type, "x-content/video-vcd") == 0) + { + media_greeting = _("You have just inserted a Video CD."); + } + else if (strcmp (x_content_type, "x-content/video-svcd") == 0) + { + media_greeting = _("You have just inserted a Super Video CD."); + } + else if (strcmp (x_content_type, "x-content/blank-cd") == 0) + { + media_greeting = _("You have just inserted a blank CD."); + } + else if (strcmp (x_content_type, "x-content/blank-dvd") == 0) + { + media_greeting = _("You have just inserted a blank DVD."); + } + else if (strcmp (x_content_type, "x-content/blank-cd") == 0) + { + media_greeting = _("You have just inserted a blank Blu-Ray disc."); + } + else if (strcmp (x_content_type, "x-content/blank-cd") == 0) + { + media_greeting = _("You have just inserted a blank HD DVD."); + } + else if (strcmp (x_content_type, "x-content/image-photocd") == 0) + { + media_greeting = _("You have just inserted a Photo CD."); + } + else if (strcmp (x_content_type, "x-content/image-picturecd") == 0) + { + media_greeting = _("You have just inserted a Picture CD."); + } + else if (strcmp (x_content_type, "x-content/image-dcf") == 0) + { + media_greeting = _("You have just inserted a medium with digital photos."); + } + else if (strcmp (x_content_type, "x-content/audio-player") == 0) + { + media_greeting = _("You have just inserted a digital audio player."); + } + else if (g_content_type_is_a (x_content_type, "x-content/software")) + { + media_greeting = _("You have just inserted a medium with software intended to be automatically started."); + } + else + { + /* fallback to generic greeting */ + media_greeting = _("You have just inserted a medium."); + } + markup = g_strdup_printf ("<big><b>%s %s</b></big>", media_greeting, _("Choose what application to launch.")); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + + label = gtk_label_new (NULL); + content_description = g_content_type_get_description (x_content_type); + markup = g_strdup_printf (_("Select how to open \"%s\" and whether to perform this action in the future for other media of type \"%s\"."), mount_name, content_description); + g_free (content_description); + gtk_label_set_markup (GTK_LABEL (label), markup); + g_free (markup); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); + + data = g_new0 (AutorunDialogData, 1); + data->dialog = dialog; + data->mount = g_object_ref (mount); + data->remember = !pref_ask; + data->selected_ignore = pref_ignore; + data->x_content_type = g_strdup (x_content_type); + data->selected_app = g_app_info_get_default_for_type (x_content_type, FALSE); + data->open_window_func = open_window_func; + data->user_data = user_data; + + combo_box = gtk_combo_box_new (); + caja_autorun_prepare_combo_box (combo_box, x_content_type, FALSE, TRUE, FALSE, autorun_combo_changed, data); + g_signal_connect (G_OBJECT (combo_box), + "key-press-event", + G_CALLBACK (combo_box_enter_ok), + dialog); + + gtk_box_pack_start (GTK_BOX (vbox), combo_box, TRUE, TRUE, 0); + + always_check_button = gtk_check_button_new_with_mnemonic (_("_Always perform this action")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (always_check_button), data->remember); + g_signal_connect (G_OBJECT (always_check_button), + "toggled", + G_CALLBACK (autorun_always_toggled), + data); + gtk_box_pack_start (GTK_BOX (vbox), always_check_button, TRUE, TRUE, 0); + + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_OK, + NULL); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + if (g_mount_can_eject (mount)) + { + GtkWidget *eject_image; + eject_button = gtk_button_new_with_mnemonic (_("_Eject")); + pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "media-eject", + caja_get_icon_size_for_stock_size (GTK_ICON_SIZE_BUTTON), + 0, + NULL); + eject_image = gtk_image_new_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + gtk_button_set_image (GTK_BUTTON (eject_button), eject_image); + data->should_eject = TRUE; + } + else + { + eject_button = gtk_button_new_with_mnemonic (_("_Unmount")); + data->should_eject = FALSE; + } + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), eject_button, AUTORUN_DIALOG_RESPONSE_EJECT); + gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (gtk_dialog_get_action_area (GTK_DIALOG (dialog))), eject_button, TRUE); + + /* show the dialog */ + gtk_widget_show_all (dialog); + + g_signal_connect (G_OBJECT (dialog), + "response", + G_CALLBACK (autorun_dialog_response), + data); + + g_signal_connect (G_OBJECT (data->mount), + "unmounted", + G_CALLBACK (autorun_dialog_mount_unmounted), + data); + +out: + g_free (mount_name); + return ret; +} + +typedef struct +{ + GMount *mount; + CajaAutorunOpenWindow open_window_func; + gpointer user_data; +} AutorunData; + +static void +autorun_guessed_content_type_callback (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + char **guessed_content_type; + AutorunData *data = user_data; + gboolean open_folder; + + open_folder = FALSE; + + error = NULL; + guessed_content_type = g_mount_guess_content_type_finish (G_MOUNT (source_object), res, &error); + g_object_set_data_full (source_object, + "caja-content-type-cache", + g_strdupv (guessed_content_type), + (GDestroyNotify)g_strfreev); + if (error != NULL) + { + g_warning ("Unabled to guess content type for mount: %s", error->message); + g_error_free (error); + } + else + { + if (guessed_content_type != NULL && g_strv_length (guessed_content_type) > 0) + { + int n; + for (n = 0; guessed_content_type[n] != NULL; n++) + { + if (do_autorun_for_content_type (data->mount, guessed_content_type[n], + data->open_window_func, data->user_data)) + { + open_folder = TRUE; + } + } + g_strfreev (guessed_content_type); + } + else + { + if (eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTOMOUNT_OPEN)) + open_folder = TRUE; + } + } + + /* only open the folder once.. */ + if (open_folder && data->open_window_func != NULL) + { + data->open_window_func (data->mount, data->user_data); + } + + g_object_unref (data->mount); + g_free (data); +} + +void +caja_autorun (GMount *mount, CajaAutorunOpenWindow open_window_func, gpointer user_data) +{ + AutorunData *data; + + if (!should_autorun_mount (mount) || + eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTORUN_NEVER)) + { + return; + } + + data = g_new0 (AutorunData, 1); + data->mount = g_object_ref (mount); + data->open_window_func = open_window_func; + data->user_data = user_data; + + g_mount_guess_content_type (mount, + FALSE, + NULL, + autorun_guessed_content_type_callback, + data); +} + +typedef struct +{ + CajaAutorunGetContent callback; + gpointer user_data; +} GetContentTypesData; + +static void +get_types_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GetContentTypesData *data; + char **types; + + data = user_data; + types = g_mount_guess_content_type_finish (G_MOUNT (source_object), res, NULL); + + g_object_set_data_full (source_object, + "caja-content-type-cache", + g_strdupv (types), + (GDestroyNotify)g_strfreev); + + if (data->callback) + { + data->callback (types, data->user_data); + } + g_strfreev (types); + g_free (data); +} + +void +caja_autorun_get_x_content_types_for_mount_async (GMount *mount, + CajaAutorunGetContent callback, + GCancellable *cancellable, + gpointer user_data) +{ + char **cached; + GetContentTypesData *data; + + if (mount == NULL) + { + if (callback) + { + callback (NULL, user_data); + } + return; + } + + cached = g_object_get_data (G_OBJECT (mount), "caja-content-type-cache"); + if (cached != NULL) + { + if (callback) + { + callback (cached, user_data); + } + return; + } + + data = g_new (GetContentTypesData, 1); + data->callback = callback; + data->user_data = user_data; + + g_mount_guess_content_type (mount, + FALSE, + cancellable, + get_types_cb, + data); +} + + +char ** +caja_autorun_get_cached_x_content_types_for_mount (GMount *mount) +{ + char **cached; + + if (mount == NULL) + { + return NULL; + } + + cached = g_object_get_data (G_OBJECT (mount), "caja-content-type-cache"); + if (cached != NULL) + { + return g_strdupv (cached); + } + + return NULL; +} + +static gboolean +remove_allow_volume (gpointer data) +{ + GVolume *volume = data; + + g_object_set_data (G_OBJECT (volume), "caja-allow-autorun", NULL); + return FALSE; +} + +void +caja_allow_autorun_for_volume (GVolume *volume) +{ + g_object_set_data (G_OBJECT (volume), "caja-allow-autorun", GINT_TO_POINTER (1)); +} + +#define INHIBIT_AUTORUN_SECONDS 10 + +void +caja_allow_autorun_for_volume_finish (GVolume *volume) +{ + if (g_object_get_data (G_OBJECT (volume), "caja-allow-autorun") != NULL) + { + g_timeout_add_seconds_full (0, + INHIBIT_AUTORUN_SECONDS, + remove_allow_volume, + g_object_ref (volume), + g_object_unref); + } +} + +static gboolean +should_skip_native_mount_root (GFile *root) +{ + char *path; + gboolean should_skip; + + /* skip any mounts in hidden directory hierarchies */ + path = g_file_get_path (root); + should_skip = strstr (path, "/.") != NULL; + g_free (path); + + return should_skip; +} + +static gboolean +should_autorun_mount (GMount *mount) +{ + GFile *root; + GVolume *enclosing_volume; + gboolean ignore_autorun; + + ignore_autorun = TRUE; + enclosing_volume = g_mount_get_volume (mount); + if (enclosing_volume != NULL) + { + if (g_object_get_data (G_OBJECT (enclosing_volume), "caja-allow-autorun") != NULL) + { + ignore_autorun = FALSE; + g_object_set_data (G_OBJECT (enclosing_volume), "caja-allow-autorun", NULL); + } + } + + if (ignore_autorun) + { + if (enclosing_volume != NULL) + { + g_object_unref (enclosing_volume); + } + return FALSE; + } + + root = g_mount_get_root (mount); + + /* only do autorun on local files or files where g_volume_should_automount() returns TRUE */ + ignore_autorun = TRUE; + if ((g_file_is_native (root) && !should_skip_native_mount_root (root)) || + (enclosing_volume != NULL && g_volume_should_automount (enclosing_volume))) + { + ignore_autorun = FALSE; + } + if (enclosing_volume != NULL) + { + g_object_unref (enclosing_volume); + } + g_object_unref (root); + + return !ignore_autorun; +} |