diff options
author | Perberos <[email protected]> | 2011-12-01 22:24:23 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-12-01 22:24:23 -0300 |
commit | 0e004c696b0e68b2cff37a4c3315b022a35eaf43 (patch) | |
tree | 43261e815529cb9518ed7be37af13b846af8b26b /src/caja-application.c | |
download | caja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.bz2 caja-0e004c696b0e68b2cff37a4c3315b022a35eaf43.tar.xz |
moving from https://github.com/perberos/mate-desktop-environment
Diffstat (limited to 'src/caja-application.c')
-rw-r--r-- | src/caja-application.c | 2338 |
1 files changed, 2338 insertions, 0 deletions
diff --git a/src/caja-application.c b/src/caja-application.c new file mode 100644 index 00000000..af2b586d --- /dev/null +++ b/src/caja-application.c @@ -0,0 +1,2338 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */ + +/* + * Caja + * + * Copyright (C) 1999, 2000 Red Hat, Inc. + * Copyright (C) 2000, 2001 Eazel, 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. + * + * Caja 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Authors: Elliot Lee <[email protected]>, + * Darin Adler <[email protected]> + * + */ + +#include <config.h> +#include "caja-application.h" + +#include "file-manager/fm-desktop-icon-view.h" +#include "file-manager/fm-icon-view.h" +#include "file-manager/fm-list-view.h" +#include "file-manager/fm-tree-view.h" +#if ENABLE_EMPTY_VIEW +#include "file-manager/fm-empty-view.h" +#endif /* ENABLE_EMPTY_VIEW */ +#include "caja-information-panel.h" +#include "caja-history-sidebar.h" +#include "caja-places-sidebar.h" +#include "caja-notes-viewer.h" +#include "caja-emblem-sidebar.h" +#include "caja-image-properties-page.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> +#include "caja-desktop-window.h" +#include "caja-main.h" +#include "caja-spatial-window.h" +#include "caja-navigation-window.h" +#include "caja-window-slot.h" +#include "caja-navigation-window-slot.h" +#include "caja-window-bookmarks.h" +#include "libcaja-private/caja-file-operations.h" +#include "caja-window-private.h" +#include "caja-window-manage-views.h" +#include <unistd.h> +#include <libxml/xmlsave.h> +#include <glib/gstdio.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-stock-dialogs.h> +#include <gdk/gdkx.h> +#include <gtk/gtk.h> +#include <libcaja-private/caja-debug-log.h> +#include <libcaja-private/caja-file-utilities.h> +#include <libcaja-private/caja-global-preferences.h> +#include <libcaja-private/caja-module.h> +#include <libcaja-private/caja-undo-manager.h> +#include <libcaja-private/caja-desktop-link-monitor.h> +#include <libcaja-private/caja-directory-private.h> +#include <libcaja-private/caja-signaller.h> +#include <libcaja-extension/caja-menu-provider.h> +#include <libcaja-private/caja-autorun.h> + +enum { + COMMAND_0, /* unused: 0 is an invalid command */ + + COMMAND_START_DESKTOP, + COMMAND_STOP_DESKTOP, + COMMAND_OPEN_BROWSER, +}; + +/* Keep window from shrinking down ridiculously small; numbers are somewhat arbitrary */ +#define APPLICATION_WINDOW_MIN_WIDTH 300 +#define APPLICATION_WINDOW_MIN_HEIGHT 100 + +#define START_STATE_CONFIG "start-state" + +#define CAJA_ACCEL_MAP_SAVE_DELAY 30 + +/* Keeps track of all the desktop windows. */ +static GList *caja_application_desktop_windows; + +/* Keeps track of all the caja windows. */ +static GList *caja_application_window_list; + +/* Keeps track of all the object windows */ +static GList *caja_application_spatial_window_list; + +/* The saving of the accelerator map was requested */ +static gboolean save_of_accel_map_requested = FALSE; + +static void desktop_changed_callback (gpointer user_data); +static void desktop_location_changed_callback (gpointer user_data); +static void mount_removed_callback (GVolumeMonitor *monitor, + GMount *mount, + CajaApplication *application); +static void mount_added_callback (GVolumeMonitor *monitor, + GMount *mount, + CajaApplication *application); +static void volume_added_callback (GVolumeMonitor *monitor, + GVolume *volume, + CajaApplication *application); +static void drive_connected_callback (GVolumeMonitor *monitor, + GDrive *drive, + CajaApplication *application); +static void drive_listen_for_eject_button (GDrive *drive, + CajaApplication *application); +static void caja_application_load_session (CajaApplication *application); +static char * caja_application_get_session_data (void); + +G_DEFINE_TYPE (CajaApplication, caja_application, G_TYPE_OBJECT); + +static gboolean +_unique_message_data_set_geometry_and_uris (UniqueMessageData *message_data, + const char *geometry, + char **uris) +{ + GString *list; + gint i; + gchar *result; + gsize length; + + list = g_string_new (NULL); + if (geometry != NULL) + { + g_string_append (list, geometry); + } + g_string_append (list, "\r\n"); + + for (i = 0; uris != NULL && uris[i]; i++) + { + g_string_append (list, uris[i]); + g_string_append (list, "\r\n"); + } + + result = g_convert (list->str, list->len, + "ASCII", "UTF-8", + NULL, &length, NULL); + g_string_free (list, TRUE); + + if (result) + { + unique_message_data_set (message_data, (guchar *) result, length); + g_free (result); + return TRUE; + } + + return FALSE; +} + +static gchar ** +_unique_message_data_get_geometry_and_uris (UniqueMessageData *message_data, + char **geometry) +{ + gchar **result = NULL; + + *geometry = NULL; + + gchar *text, *newline, *uris; + text = unique_message_data_get_text (message_data); + if (text) + { + newline = strchr (text, '\n'); + if (newline) + { + *geometry = g_strndup (text, newline-text); + uris = newline+1; + } + else + { + uris = text; + } + + result = g_uri_list_extract_uris (uris); + g_free (text); + } + + /* if the string is empty, make it NULL */ + if (*geometry && strlen (*geometry) == 0) + { + g_free (*geometry); + *geometry = NULL; + } + + return result; +} + +GList * +caja_application_get_window_list (void) +{ + return caja_application_window_list; +} + +GList * +caja_application_get_spatial_window_list (void) +{ + return caja_application_spatial_window_list; +} + +unsigned int +caja_application_get_n_windows (void) +{ + return g_list_length (caja_application_window_list) + + g_list_length (caja_application_desktop_windows); +} + +static void +startup_volume_mount_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + g_volume_mount_finish (G_VOLUME (source_object), res, NULL); +} + +static void +automount_all_volumes (CajaApplication *application) +{ + GList *volumes, *l; + GMount *mount; + GVolume *volume; + + if (eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTOMOUNT)) + { + /* automount all mountable volumes at start-up */ + volumes = g_volume_monitor_get_volumes (application->volume_monitor); + for (l = volumes; l != NULL; l = l->next) + { + volume = l->data; + + if (!g_volume_should_automount (volume) || + !g_volume_can_mount (volume)) + { + continue; + } + + mount = g_volume_get_mount (volume); + if (mount != NULL) + { + g_object_unref (mount); + continue; + } + + /* pass NULL as GMountOperation to avoid user interaction */ + g_volume_mount (volume, 0, NULL, NULL, startup_volume_mount_cb, NULL); + } + eel_g_object_list_free (volumes); + } + +} + +static void +smclient_save_state_cb (EggSMClient *client, + GKeyFile *state_file, + CajaApplication *application) +{ + char *data; + + data = caja_application_get_session_data (); + if (data) + { + g_key_file_set_string (state_file, + "Caja", + "documents", + data); + } + g_free (data); +} + +static void +smclient_quit_cb (EggSMClient *client, + CajaApplication *application) +{ + caja_main_event_loop_quit (TRUE); +} + +static void +caja_application_init (CajaApplication *application) +{ + /* Create an undo manager */ + application->undo_manager = caja_undo_manager_new (); + + application->unique_app = unique_app_new_with_commands ("org.mate.Caja", NULL, + "start_desktop", COMMAND_START_DESKTOP, + "stop_desktop", COMMAND_STOP_DESKTOP, + "open_browser", COMMAND_OPEN_BROWSER, + NULL); + + + application->smclient = egg_sm_client_get (); + g_signal_connect (application->smclient, "save_state", + G_CALLBACK (smclient_save_state_cb), + application); + g_signal_connect (application->smclient, "quit", + G_CALLBACK (smclient_quit_cb), + application); + /* TODO: Should connect to quit_requested and block logout on active transfer? */ + + /* register views */ + fm_icon_view_register (); + fm_desktop_icon_view_register (); + fm_list_view_register (); + fm_compact_view_register (); +#if ENABLE_EMPTY_VIEW + fm_empty_view_register (); +#endif /* ENABLE_EMPTY_VIEW */ + + /* register sidebars */ + caja_places_sidebar_register (); + caja_information_panel_register (); + fm_tree_view_register (); + caja_history_sidebar_register (); + caja_notes_viewer_register (); /* also property page */ + caja_emblem_sidebar_register (); + + /* register property pages */ + caja_image_properties_page_register (); + + /* initialize search path for custom icons */ + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + CAJA_DATADIR G_DIR_SEPARATOR_S "icons"); +} + +CajaApplication * +caja_application_new (void) +{ + return g_object_new (CAJA_TYPE_APPLICATION, NULL); +} + +static void +caja_application_finalize (GObject *object) +{ + CajaApplication *application; + + application = CAJA_APPLICATION (object); + + caja_bookmarks_exiting (); + + g_object_unref (application->undo_manager); + + if (application->volume_monitor) + { + g_object_unref (application->volume_monitor); + application->volume_monitor = NULL; + } + + g_object_unref (application->unique_app); + + if (application->automount_idle_id != 0) + { + g_source_remove (application->automount_idle_id); + application->automount_idle_id = 0; + } + + if (application->proxy != NULL) + { + g_object_unref (application->proxy); + application->proxy = NULL; + } + + G_OBJECT_CLASS (caja_application_parent_class)->finalize (object); +} + +static gboolean +check_required_directories (CajaApplication *application) +{ + char *user_directory; + char *desktop_directory; + GSList *directories; + gboolean ret; + + g_assert (CAJA_IS_APPLICATION (application)); + + ret = TRUE; + + user_directory = caja_get_user_directory (); + desktop_directory = caja_get_desktop_directory (); + + directories = NULL; + + if (!g_file_test (user_directory, G_FILE_TEST_IS_DIR)) + { + directories = g_slist_prepend (directories, user_directory); + } + + if (!g_file_test (desktop_directory, G_FILE_TEST_IS_DIR)) + { + directories = g_slist_prepend (directories, desktop_directory); + } + + if (directories != NULL) + { + int failed_count; + GString *directories_as_string; + GSList *l; + char *error_string; + const char *detail_string; + GtkDialog *dialog; + + ret = FALSE; + + failed_count = g_slist_length (directories); + + directories_as_string = g_string_new ((const char *)directories->data); + for (l = directories->next; l != NULL; l = l->next) + { + g_string_append_printf (directories_as_string, ", %s", (const char *)l->data); + } + + if (failed_count == 1) + { + error_string = g_strdup_printf (_("Caja could not create the required folder \"%s\"."), + directories_as_string->str); + detail_string = _("Before running Caja, please create the following folder, or " + "set permissions such that Caja can create it."); + } + else + { + error_string = g_strdup_printf (_("Caja could not create the following required folders: " + "%s."), directories_as_string->str); + detail_string = _("Before running Caja, please create these folders, or " + "set permissions such that Caja can create them."); + } + + dialog = eel_show_error_dialog (error_string, detail_string, NULL); + /* We need the main event loop so the user has a chance to see the dialog. */ + caja_main_event_loop_register (GTK_OBJECT (dialog)); + + g_string_free (directories_as_string, TRUE); + g_free (error_string); + } + + g_slist_free (directories); + g_free (user_directory); + g_free (desktop_directory); + + return ret; +} + +static void +menu_provider_items_updated_handler (CajaMenuProvider *provider, GtkWidget* parent_window, gpointer data) +{ + + g_signal_emit_by_name (caja_signaller_get_current (), + "popup_menu_changed"); +} + +static void +menu_provider_init_callback (void) +{ + GList *items; + GList *providers; + GList *l; + + providers = caja_module_get_extensions_for_type (CAJA_TYPE_MENU_PROVIDER); + items = NULL; + + for (l = providers; l != NULL; l = l->next) + { + CajaMenuProvider *provider = CAJA_MENU_PROVIDER (l->data); + + g_signal_connect_after (G_OBJECT (provider), "items_updated", + (GCallback)menu_provider_items_updated_handler, + NULL); + } + + caja_module_extension_list_free (providers); +} + +static gboolean +automount_all_volumes_idle_cb (gpointer data) +{ + CajaApplication *application = CAJA_APPLICATION (data); + + automount_all_volumes (application); + + application->automount_idle_id = 0; + return FALSE; +} + +static void +mark_desktop_files_trusted (void) +{ + char *do_once_file; + GFile *f, *c; + GFileEnumerator *e; + GFileInfo *info; + const char *name; + int fd; + + do_once_file = g_build_filename (g_get_user_data_dir (), + ".converted-launchers", NULL); + + if (g_file_test (do_once_file, G_FILE_TEST_EXISTS)) + { + goto out; + } + + f = caja_get_desktop_location (); + e = g_file_enumerate_children (f, + G_FILE_ATTRIBUTE_STANDARD_TYPE "," + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE + , + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + NULL, NULL); + if (e == NULL) + { + goto out2; + } + + while ((info = g_file_enumerator_next_file (e, NULL, NULL)) != NULL) + { + name = g_file_info_get_name (info); + + if (g_str_has_suffix (name, ".desktop") && + !g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_EXECUTE)) + { + c = g_file_get_child (f, name); + caja_file_mark_desktop_file_trusted (c, + NULL, FALSE, + NULL, NULL); + g_object_unref (c); + } + g_object_unref (info); + } + + g_object_unref (e); +out2: + fd = g_creat (do_once_file, 0666); + close (fd); + + g_object_unref (f); +out: + g_free (do_once_file); +} + +#define CK_NAME "org.freedesktop.ConsoleKit" +#define CK_PATH "/org/freedesktop/ConsoleKit" +#define CK_INTERFACE "org.freedesktop.ConsoleKit" + +static void +ck_session_proxy_signal_cb (GDBusProxy *proxy, + const char *sender_name, + const char *signal_name, + GVariant *parameters, + gpointer user_data) +{ + CajaApplication *application = user_data; + + if (g_strcmp0 (signal_name, "ActiveChanged") == 0) + { + g_variant_get (parameters, "(b)", &application->session_is_active); + } +} + +static void +ck_call_is_active_cb (GDBusProxy *proxy, + GAsyncResult *result, + gpointer user_data) +{ + CajaApplication *application = user_data; + GVariant *variant; + GError *error = NULL; + + variant = g_dbus_proxy_call_finish (proxy, result, &error); + + if (variant == NULL) + { + g_warning ("Error when calling IsActive(): %s\n", error->message); + application->session_is_active = TRUE; + + g_error_free (error); + return; + } + + g_variant_get (variant, "(b)", &application->session_is_active); + + g_variant_unref (variant); +} + +static void +session_proxy_appeared (GObject *source, + GAsyncResult *res, + gpointer user_data) +{ + CajaApplication *application = user_data; + GDBusProxy *proxy; + GError *error = NULL; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + + if (error != NULL) + { + g_warning ("Failed to get the current CK session: %s", error->message); + g_error_free (error); + + application->session_is_active = TRUE; + return; + } + + g_signal_connect (proxy, "g-signal", + G_CALLBACK (ck_session_proxy_signal_cb), + application); + + g_dbus_proxy_call (proxy, + "IsActive", + g_variant_new ("()"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) ck_call_is_active_cb, + application); + + application->proxy = proxy; +} + +static void +ck_get_current_session_cb (GDBusConnection *connection, + GAsyncResult *result, + gpointer user_data) +{ + CajaApplication *application = user_data; + GVariant *variant; + const char *session_path = NULL; + GError *error = NULL; + + variant = g_dbus_connection_call_finish (connection, result, &error); + + if (variant == NULL) + { + g_warning ("Failed to get the current CK session: %s", error->message); + g_error_free (error); + + application->session_is_active = TRUE; + return; + } + + g_variant_get (variant, "(&o)", &session_path); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + CK_NAME, + session_path, + CK_INTERFACE ".Session", + NULL, + session_proxy_appeared, + application); + + g_variant_unref (variant); +} + +static void +do_initialize_consolekit (CajaApplication *application) +{ + GDBusConnection *connection; + + connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, NULL); + + if (connection == NULL) + { + application->session_is_active = TRUE; + return; + } + + g_dbus_connection_call (connection, + CK_NAME, + CK_PATH "/Manager", + CK_INTERFACE ".Manager", + "GetCurrentSession", + g_variant_new ("()"), + G_VARIANT_TYPE ("(o)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + (GAsyncReadyCallback) ck_get_current_session_cb, + application); + + g_object_unref (connection); +} + +static void +do_upgrades_once (CajaApplication *application, + gboolean no_desktop) +{ + char *metafile_dir, *updated; + int fd; + + if (!no_desktop) + { + mark_desktop_files_trusted (); + } + + metafile_dir = g_build_filename(g_get_home_dir(), ".config", "caja", "metafiles", NULL); + + if (g_file_test (metafile_dir, G_FILE_TEST_IS_DIR)) + { + updated = g_build_filename (metafile_dir, "migrated-to-gvfs", NULL); + if (!g_file_test (updated, G_FILE_TEST_EXISTS)) + { + g_spawn_command_line_async (LIBEXECDIR "/caja-convert-metadata --quiet", NULL); + fd = g_creat (updated, 0600); + if (fd != -1) + { + close (fd); + } + } + g_free (updated); + } + g_free (metafile_dir); +} + +static void +finish_startup (CajaApplication *application, + gboolean no_desktop) +{ + GList *drives; + + do_upgrades_once (application, no_desktop); + + /* initialize caja modules */ + caja_module_setup (); + + /* attach menu-provider module callback */ + menu_provider_init_callback (); + + /* Initialize the desktop link monitor singleton */ + caja_desktop_link_monitor_get (); + + /* Initialize the ConsoleKit listener for active session */ + do_initialize_consolekit (application); + + /* Watch for mounts so we can restore open windows This used + * to be for showing new window on mount, but is not used + * anymore */ + + /* Watch for unmounts so we can close open windows */ + /* TODO-gio: This should be using the UNMOUNTED feature of GFileMonitor instead */ + application->volume_monitor = g_volume_monitor_get (); + g_signal_connect_object (application->volume_monitor, "mount_removed", + G_CALLBACK (mount_removed_callback), application, 0); + g_signal_connect_object (application->volume_monitor, "mount_pre_unmount", + G_CALLBACK (mount_removed_callback), application, 0); + g_signal_connect_object (application->volume_monitor, "mount_added", + G_CALLBACK (mount_added_callback), application, 0); + g_signal_connect_object (application->volume_monitor, "volume_added", + G_CALLBACK (volume_added_callback), application, 0); + g_signal_connect_object (application->volume_monitor, "drive_connected", + G_CALLBACK (drive_connected_callback), application, 0); + + /* listen for eject button presses */ + drives = g_volume_monitor_get_connected_drives (application->volume_monitor); + g_list_foreach (drives, (GFunc) drive_listen_for_eject_button, application); + g_list_foreach (drives, (GFunc) g_object_unref, NULL); + g_list_free (drives); + + application->automount_idle_id = + g_idle_add_full (G_PRIORITY_LOW, + automount_all_volumes_idle_cb, + application, NULL); +} + +static void +open_window (CajaApplication *application, + const char *startup_id, + const char *uri, GdkScreen *screen, const char *geometry, + gboolean browser_window) +{ + GFile *location; + CajaWindow *window; + + if (browser_window || + eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) + { + window = caja_application_create_navigation_window (application, + startup_id, + screen); + if (uri == NULL) + { + caja_window_go_home (window); + } + else + { + location = g_file_new_for_uri (uri); + caja_window_go_to (window, location); + g_object_unref (location); + } + } + else + { + if (uri == NULL) + { + location = g_file_new_for_path (g_get_home_dir ()); + } + else + { + location = g_file_new_for_uri (uri); + } + + window = caja_application_present_spatial_window (application, + NULL, + startup_id, + location, + screen); + g_object_unref (location); + } + + if (geometry != NULL && !gtk_widget_get_visible (GTK_WIDGET (window))) + { + /* never maximize windows opened from shell if a + * custom geometry has been requested. + */ + gtk_window_unmaximize (GTK_WINDOW (window)); + eel_gtk_window_set_initial_geometry_from_string (GTK_WINDOW (window), + geometry, + APPLICATION_WINDOW_MIN_WIDTH, + APPLICATION_WINDOW_MIN_HEIGHT, + FALSE); + } +} + +static void +open_windows (CajaApplication *application, + const char *startup_id, + char **uris, + GdkScreen *screen, + const char *geometry, + gboolean browser_window) +{ + guint i; + + if (uris == NULL || uris[0] == NULL) + { + /* Open a window pointing at the default location. */ + open_window (application, startup_id, NULL, screen, geometry, browser_window); + } + else + { + /* Open windows at each requested location. */ + for (i = 0; uris[i] != NULL; i++) + { + open_window (application, startup_id, uris[i], screen, geometry, browser_window); + } + } +} + +static UniqueResponse +message_received_cb (UniqueApp *unique_app, + gint command, + UniqueMessageData *message, + guint time_, + gpointer user_data) +{ + CajaApplication *application; + UniqueResponse res; + char **uris; + char *geometry; + GdkScreen *screen; + + application = user_data; + res = UNIQUE_RESPONSE_OK; + + switch (command) + { + case UNIQUE_CLOSE: + res = UNIQUE_RESPONSE_OK; + caja_main_event_loop_quit (TRUE); + + break; + case UNIQUE_OPEN: + case COMMAND_OPEN_BROWSER: + uris = _unique_message_data_get_geometry_and_uris (message, &geometry); + screen = unique_message_data_get_screen (message); + open_windows (application, + unique_message_data_get_startup_id (message), + uris, + screen, + geometry, + command == COMMAND_OPEN_BROWSER); + g_strfreev (uris); + g_free (geometry); + break; + case COMMAND_START_DESKTOP: + caja_application_open_desktop (application); + break; + case COMMAND_STOP_DESKTOP: + caja_application_close_desktop (); + break; + default: + res = UNIQUE_RESPONSE_PASSTHROUGH; + break; + } + + return res; +} + +gboolean +caja_application_save_accel_map (gpointer data) +{ + if (save_of_accel_map_requested) + { + char *accel_map_filename; + accel_map_filename = caja_get_accel_map_file (); + if (accel_map_filename) + { + gtk_accel_map_save (accel_map_filename); + g_free (accel_map_filename); + } + save_of_accel_map_requested = FALSE; + } + + return FALSE; +} + + +static void +queue_accel_map_save_callback (GtkAccelMap *object, gchar *accel_path, + guint accel_key, GdkModifierType accel_mods, + gpointer user_data) +{ + if (!save_of_accel_map_requested) + { + save_of_accel_map_requested = TRUE; + g_timeout_add_seconds (CAJA_ACCEL_MAP_SAVE_DELAY, + caja_application_save_accel_map, NULL); + } +} + +void +caja_application_startup (CajaApplication *application, + gboolean kill_shell, + gboolean no_default_window, + gboolean no_desktop, + gboolean browser_window, + const char *geometry, + char **urls) +{ + UniqueMessageData *message; + + /* Check the user's ~/.config/caja directories and post warnings + * if there are problems. + */ + if (!kill_shell && !check_required_directories (application)) + { + return; + } + + if (kill_shell) + { + if (unique_app_is_running (application->unique_app)) + { + unique_app_send_message (application->unique_app, + UNIQUE_CLOSE, NULL); + + } + } + else + { + char *accel_map_filename; + + if (!no_desktop && + !eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_DESKTOP)) + { + no_desktop = TRUE; + } + + if (!no_desktop) + { + if (unique_app_is_running (application->unique_app)) + { + unique_app_send_message (application->unique_app, + COMMAND_START_DESKTOP, NULL); + } + else + { + caja_application_open_desktop (application); + } + } + + if (!unique_app_is_running (application->unique_app)) + { + finish_startup (application, no_desktop); + g_signal_connect (application->unique_app, "message-received", G_CALLBACK (message_received_cb), application); + } + + /* Monitor the preference to show or hide the desktop */ + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_SHOW_DESKTOP, + desktop_changed_callback, + application, + G_OBJECT (application)); + + /* Monitor the preference to have the desktop */ + /* point to the Unix home folder */ + eel_preferences_add_callback_while_alive (CAJA_PREFERENCES_DESKTOP_IS_HOME_DIR, + desktop_location_changed_callback, + NULL, + G_OBJECT (application)); + + /* Create the other windows. */ + if (urls != NULL || !no_default_window) + { + if (unique_app_is_running (application->unique_app)) + { + message = unique_message_data_new (); + _unique_message_data_set_geometry_and_uris (message, geometry, urls); + if (browser_window) + { + unique_app_send_message (application->unique_app, + COMMAND_OPEN_BROWSER, message); + } + else + { + unique_app_send_message (application->unique_app, + UNIQUE_OPEN, message); + } + unique_message_data_free (message); + } + else + { + open_windows (application, NULL, + urls, + gdk_screen_get_default (), + geometry, + browser_window); + } + } + + /* Load session info if availible */ + caja_application_load_session (application); + + /* load accelerator map, and register save callback */ + accel_map_filename = caja_get_accel_map_file (); + if (accel_map_filename) + { + gtk_accel_map_load (accel_map_filename); + g_free (accel_map_filename); + } + g_signal_connect (gtk_accel_map_get (), "changed", G_CALLBACK (queue_accel_map_save_callback), NULL); + } +} + + +static void +selection_get_cb (GtkWidget *widget, + GtkSelectionData *selection_data, + guint info, + guint time) +{ + /* No extra targets atm */ +} + +static GtkWidget * +get_desktop_manager_selection (GdkDisplay *display, int screen) +{ + char selection_name[32]; + GdkAtom selection_atom; + Window selection_owner; + GtkWidget *selection_widget; + + g_snprintf (selection_name, sizeof (selection_name), "_NET_DESKTOP_MANAGER_S%d", screen); + selection_atom = gdk_atom_intern (selection_name, FALSE); + + selection_owner = XGetSelectionOwner (GDK_DISPLAY_XDISPLAY (display), + gdk_x11_atom_to_xatom_for_display (display, + selection_atom)); + if (selection_owner != None) + { + return NULL; + } + + selection_widget = gtk_invisible_new_for_screen (gdk_display_get_screen (display, screen)); + /* We need this for gdk_x11_get_server_time() */ + gtk_widget_add_events (selection_widget, GDK_PROPERTY_CHANGE_MASK); + + if (gtk_selection_owner_set_for_display (display, + selection_widget, + selection_atom, + gdk_x11_get_server_time (gtk_widget_get_window (selection_widget)))) + { + + g_signal_connect (selection_widget, "selection_get", + G_CALLBACK (selection_get_cb), NULL); + return selection_widget; + } + + gtk_widget_destroy (selection_widget); + + return NULL; +} + +static void +desktop_unrealize_cb (GtkWidget *widget, + GtkWidget *selection_widget) +{ + gtk_widget_destroy (selection_widget); +} + +static gboolean +selection_clear_event_cb (GtkWidget *widget, + GdkEventSelection *event, + CajaDesktopWindow *window) +{ + gtk_widget_destroy (GTK_WIDGET (window)); + + caja_application_desktop_windows = + g_list_remove (caja_application_desktop_windows, window); + + return TRUE; +} + +static void +caja_application_create_desktop_windows (CajaApplication *application) +{ + static gboolean create_in_progress = FALSE; + GdkDisplay *display; + CajaDesktopWindow *window; + GtkWidget *selection_widget; + int screens, i; + + g_return_if_fail (caja_application_desktop_windows == NULL); + g_return_if_fail (CAJA_IS_APPLICATION (application)); + + if (create_in_progress) + { + return; + } + + create_in_progress = TRUE; + + display = gdk_display_get_default (); + screens = gdk_display_get_n_screens (display); + + for (i = 0; i < screens; i++) + { + selection_widget = get_desktop_manager_selection (display, i); + if (selection_widget != NULL) + { + window = caja_desktop_window_new (application, + gdk_display_get_screen (display, i)); + + g_signal_connect (selection_widget, "selection_clear_event", + G_CALLBACK (selection_clear_event_cb), window); + + g_signal_connect (window, "unrealize", + G_CALLBACK (desktop_unrealize_cb), selection_widget); + + /* We realize it immediately so that the CAJA_DESKTOP_WINDOW_ID + property is set so mate-settings-daemon doesn't try to set the + background. And we do a gdk_flush() to be sure X gets it. */ + gtk_widget_realize (GTK_WIDGET (window)); + gdk_flush (); + + + caja_application_desktop_windows = + g_list_prepend (caja_application_desktop_windows, window); + } + } + + create_in_progress = FALSE; +} + +void +caja_application_open_desktop (CajaApplication *application) +{ + if (caja_application_desktop_windows == NULL) + { + caja_application_create_desktop_windows (application); + } +} + +void +caja_application_close_desktop (void) +{ + if (caja_application_desktop_windows != NULL) + { + g_list_foreach (caja_application_desktop_windows, + (GFunc) gtk_widget_destroy, NULL); + g_list_free (caja_application_desktop_windows); + caja_application_desktop_windows = NULL; + } +} + +void +caja_application_close_all_navigation_windows (void) +{ + GList *list_copy; + GList *l; + + list_copy = g_list_copy (caja_application_window_list); + /* First hide all window to get the feeling of quick response */ + for (l = list_copy; l != NULL; l = l->next) + { + CajaWindow *window; + + window = CAJA_WINDOW (l->data); + + if (CAJA_IS_NAVIGATION_WINDOW (window)) + { + gtk_widget_hide (GTK_WIDGET (window)); + } + } + + for (l = list_copy; l != NULL; l = l->next) + { + CajaWindow *window; + + window = CAJA_WINDOW (l->data); + + if (CAJA_IS_NAVIGATION_WINDOW (window)) + { + caja_window_close (window); + } + } + g_list_free (list_copy); +} + +static CajaSpatialWindow * +caja_application_get_existing_spatial_window (GFile *location) +{ + GList *l; + CajaWindowSlot *slot; + + for (l = caja_application_get_spatial_window_list (); + l != NULL; l = l->next) + { + GFile *window_location; + + slot = CAJA_WINDOW (l->data)->details->active_pane->active_slot; + window_location = slot->location; + if (window_location != NULL) + { + if (g_file_equal (location, window_location)) + { + return CAJA_SPATIAL_WINDOW (l->data); + } + } + } + return NULL; +} + +static CajaSpatialWindow * +find_parent_spatial_window (CajaSpatialWindow *window) +{ + CajaFile *file; + CajaFile *parent_file; + CajaWindowSlot *slot; + GFile *location; + + slot = CAJA_WINDOW (window)->details->active_pane->active_slot; + + location = slot->location; + if (location == NULL) + { + return NULL; + } + file = caja_file_get (location); + + if (!file) + { + return NULL; + } + + parent_file = caja_file_get_parent (file); + caja_file_unref (file); + while (parent_file) + { + CajaSpatialWindow *parent_window; + + location = caja_file_get_location (parent_file); + parent_window = caja_application_get_existing_spatial_window (location); + g_object_unref (location); + + /* Stop at the desktop directory if it's not explicitely opened + * in a spatial window of its own. + */ + if (caja_file_is_desktop_directory (parent_file) && !parent_window) + { + caja_file_unref (parent_file); + return NULL; + } + + if (parent_window) + { + caja_file_unref (parent_file); + return parent_window; + } + file = parent_file; + parent_file = caja_file_get_parent (file); + caja_file_unref (file); + } + + return NULL; +} + +void +caja_application_close_parent_windows (CajaSpatialWindow *window) +{ + CajaSpatialWindow *parent_window; + CajaSpatialWindow *new_parent_window; + + g_return_if_fail (CAJA_IS_SPATIAL_WINDOW (window)); + + parent_window = find_parent_spatial_window (window); + + while (parent_window) + { + + new_parent_window = find_parent_spatial_window (parent_window); + caja_window_close (CAJA_WINDOW (parent_window)); + parent_window = new_parent_window; + } +} + +void +caja_application_close_all_spatial_windows (void) +{ + GList *list_copy; + GList *l; + + list_copy = g_list_copy (caja_application_spatial_window_list); + /* First hide all window to get the feeling of quick response */ + for (l = list_copy; l != NULL; l = l->next) + { + CajaWindow *window; + + window = CAJA_WINDOW (l->data); + + if (CAJA_IS_SPATIAL_WINDOW (window)) + { + gtk_widget_hide (GTK_WIDGET (window)); + } + } + + for (l = list_copy; l != NULL; l = l->next) + { + CajaWindow *window; + + window = CAJA_WINDOW (l->data); + + if (CAJA_IS_SPATIAL_WINDOW (window)) + { + caja_window_close (window); + } + } + g_list_free (list_copy); +} + +static void +caja_application_destroyed_window (GtkObject *object, CajaApplication *application) +{ + caja_application_window_list = g_list_remove (caja_application_window_list, object); +} + +static gboolean +caja_window_delete_event_callback (GtkWidget *widget, + GdkEvent *event, + gpointer user_data) +{ + CajaWindow *window; + + window = CAJA_WINDOW (widget); + caja_window_close (window); + + return TRUE; +} + + +static CajaWindow * +create_window (CajaApplication *application, + GType window_type, + const char *startup_id, + GdkScreen *screen) +{ + CajaWindow *window; + + g_return_val_if_fail (CAJA_IS_APPLICATION (application), NULL); + + window = CAJA_WINDOW (gtk_widget_new (window_type, + "app", application, + "screen", screen, + NULL)); + + if (startup_id) + { + gtk_window_set_startup_id (GTK_WINDOW (window), startup_id); + } + + g_signal_connect_data (window, "delete_event", + G_CALLBACK (caja_window_delete_event_callback), NULL, NULL, + G_CONNECT_AFTER); + + g_signal_connect_object (window, "destroy", + G_CALLBACK (caja_application_destroyed_window), application, 0); + + caja_application_window_list = g_list_prepend (caja_application_window_list, window); + + /* Do not yet show the window. It will be shown later on if it can + * successfully display its initial URI. Otherwise it will be destroyed + * without ever having seen the light of day. + */ + + return window; +} + +static void +spatial_window_destroyed_callback (void *user_data, GObject *window) +{ + caja_application_spatial_window_list = g_list_remove (caja_application_spatial_window_list, window); + +} + +CajaWindow * +caja_application_present_spatial_window (CajaApplication *application, + CajaWindow *requesting_window, + const char *startup_id, + GFile *location, + GdkScreen *screen) +{ + return caja_application_present_spatial_window_with_selection (application, + requesting_window, + startup_id, + location, + NULL, + screen); +} + +CajaWindow * +caja_application_present_spatial_window_with_selection (CajaApplication *application, + CajaWindow *requesting_window, + const char *startup_id, + GFile *location, + GList *new_selection, + GdkScreen *screen) +{ + CajaWindow *window; + GList *l; + char *uri; + + g_return_val_if_fail (CAJA_IS_APPLICATION (application), NULL); + + for (l = caja_application_get_spatial_window_list (); + l != NULL; l = l->next) + { + CajaWindow *existing_window; + CajaWindowSlot *slot; + GFile *existing_location; + + existing_window = CAJA_WINDOW (l->data); + slot = existing_window->details->active_pane->active_slot; + existing_location = slot->pending_location; + + if (existing_location == NULL) + { + existing_location = slot->location; + } + + if (g_file_equal (existing_location, location)) + { + gtk_window_present (GTK_WINDOW (existing_window)); + if (new_selection && + slot->content_view != NULL) + { + caja_view_set_selection (slot->content_view, new_selection); + } + + uri = g_file_get_uri (location); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "present EXISTING spatial window=%p: %s", + existing_window, uri); + g_free (uri); + return existing_window; + } + } + + window = create_window (application, CAJA_TYPE_SPATIAL_WINDOW, startup_id, screen); + if (requesting_window) + { + /* Center the window over the requesting window by default */ + int orig_x, orig_y, orig_width, orig_height; + int new_x, new_y, new_width, new_height; + + gtk_window_get_position (GTK_WINDOW (requesting_window), + &orig_x, &orig_y); + gtk_window_get_size (GTK_WINDOW (requesting_window), + &orig_width, &orig_height); + gtk_window_get_default_size (GTK_WINDOW (window), + &new_width, &new_height); + + new_x = orig_x + (orig_width - new_width) / 2; + new_y = orig_y + (orig_height - new_height) / 2; + + if (orig_width - new_width < 10) + { + new_x += 10; + new_y += 10; + } + + gtk_window_move (GTK_WINDOW (window), new_x, new_y); + } + + caja_application_spatial_window_list = g_list_prepend (caja_application_spatial_window_list, window); + g_object_weak_ref (G_OBJECT (window), + spatial_window_destroyed_callback, NULL); + + caja_window_go_to_with_selection (window, location, new_selection); + + uri = g_file_get_uri (location); + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "present NEW spatial window=%p: %s", + window, uri); + g_free (uri); + + return window; +} + +static gboolean +another_navigation_window_already_showing (CajaWindow *the_window) +{ + GList *list, *item; + + list = caja_application_get_window_list (); + for (item = list; item != NULL; item = item->next) + { + if (item->data != the_window && + CAJA_IS_NAVIGATION_WINDOW (item->data)) + { + return TRUE; + } + } + + return FALSE; +} + +CajaWindow * +caja_application_create_navigation_window (CajaApplication *application, + const char *startup_id, + GdkScreen *screen) +{ + CajaWindow *window; + char *geometry_string; + gboolean maximized; + + g_return_val_if_fail (CAJA_IS_APPLICATION (application), NULL); + + window = create_window (application, CAJA_TYPE_NAVIGATION_WINDOW, startup_id, screen); + + maximized = eel_preferences_get_boolean + (CAJA_PREFERENCES_NAVIGATION_WINDOW_MAXIMIZED); + if (maximized) + { + gtk_window_maximize (GTK_WINDOW (window)); + } + else + { + gtk_window_unmaximize (GTK_WINDOW (window)); + } + + geometry_string = eel_preferences_get + (CAJA_PREFERENCES_NAVIGATION_WINDOW_SAVED_GEOMETRY); + if (geometry_string != NULL && + geometry_string[0] != 0) + { + /* Ignore saved window position if a window with the same + * location is already showing. That way the two windows + * wont appear at the exact same location on the screen. + */ + eel_gtk_window_set_initial_geometry_from_string + (GTK_WINDOW (window), + geometry_string, + CAJA_NAVIGATION_WINDOW_MIN_WIDTH, + CAJA_NAVIGATION_WINDOW_MIN_HEIGHT, + another_navigation_window_already_showing (window)); + } + g_free (geometry_string); + + caja_debug_log (FALSE, CAJA_DEBUG_LOG_DOMAIN_USER, + "create new navigation window=%p", + window); + + return window; +} + +/* callback for changing the directory the desktop points to */ +static void +desktop_location_changed_callback (gpointer user_data) +{ + if (caja_application_desktop_windows != NULL) + { + g_list_foreach (caja_application_desktop_windows, + (GFunc) caja_desktop_window_update_directory, NULL); + } +} + +/* callback for showing or hiding the desktop based on the user's preference */ +static void +desktop_changed_callback (gpointer user_data) +{ + CajaApplication *application; + + application = CAJA_APPLICATION (user_data); + if ( eel_preferences_get_boolean (CAJA_PREFERENCES_SHOW_DESKTOP)) + { + caja_application_open_desktop (application); + } + else + { + caja_application_close_desktop (); + } +} + +static gboolean +window_can_be_closed (CajaWindow *window) +{ + if (!CAJA_IS_DESKTOP_WINDOW (window)) + { + return TRUE; + } + + return FALSE; +} + +static void +volume_added_callback (GVolumeMonitor *monitor, + GVolume *volume, + CajaApplication *application) +{ + if (eel_preferences_get_boolean (CAJA_PREFERENCES_MEDIA_AUTOMOUNT) && + g_volume_should_automount (volume) && + g_volume_can_mount (volume)) + { + caja_file_operations_mount_volume (NULL, volume, TRUE); + } + else + { + /* Allow caja_autorun() to run. When the mount is later + * added programmatically (i.e. for a blank CD), + * caja_autorun() will be called by mount_added_callback(). */ + caja_allow_autorun_for_volume (volume); + caja_allow_autorun_for_volume_finish (volume); + } +} + +static void +drive_eject_cb (GObject *source_object, + GAsyncResult *res, + gpointer user_data) +{ + GError *error; + char *primary; + char *name; + error = NULL; + if (!g_drive_eject_with_operation_finish (G_DRIVE (source_object), res, &error)) + { + if (error->code != G_IO_ERROR_FAILED_HANDLED) + { + name = g_drive_get_name (G_DRIVE (source_object)); + primary = g_strdup_printf (_("Unable to eject %s"), name); + g_free (name); + eel_show_error_dialog (primary, + error->message, + NULL); + g_free (primary); + } + g_error_free (error); + } +} + +static void +drive_eject_button_pressed (GDrive *drive, + CajaApplication *application) +{ + GMountOperation *mount_op; + + mount_op = gtk_mount_operation_new (NULL); + g_drive_eject_with_operation (drive, 0, mount_op, NULL, drive_eject_cb, NULL); + g_object_unref (mount_op); +} + +static void +drive_listen_for_eject_button (GDrive *drive, CajaApplication *application) +{ + g_signal_connect (drive, + "eject-button", + G_CALLBACK (drive_eject_button_pressed), + application); +} + +static void +drive_connected_callback (GVolumeMonitor *monitor, + GDrive *drive, + CajaApplication *application) +{ + drive_listen_for_eject_button (drive, application); +} + +static void +autorun_show_window (GMount *mount, gpointer user_data) +{ + GFile *location; + CajaApplication *application = user_data; + + location = g_mount_get_root (mount); + + /* Ther should probably be an easier way to do this */ + if (eel_preferences_get_boolean (CAJA_PREFERENCES_ALWAYS_USE_BROWSER)) + { + CajaWindow *window; + window = caja_application_create_navigation_window (application, + NULL, + gdk_screen_get_default ()); + caja_window_go_to (window, location); + + } + else + { + caja_application_present_spatial_window (application, + NULL, + NULL, + location, + gdk_screen_get_default ()); + } + g_object_unref (location); +} + +static void +mount_added_callback (GVolumeMonitor *monitor, + GMount *mount, + CajaApplication *application) +{ + CajaDirectory *directory; + GFile *root; + + if (!application->session_is_active) + { + return; + } + + root = g_mount_get_root (mount); + directory = caja_directory_get_existing (root); + g_object_unref (root); + if (directory != NULL) + { + caja_directory_force_reload (directory); + caja_directory_unref (directory); + } + + caja_autorun (mount, autorun_show_window, application); +} + +static CajaWindowSlot * +get_first_navigation_slot (GList *slot_list) +{ + GList *l; + + for (l = slot_list; l != NULL; l = l->next) + { + if (CAJA_IS_NAVIGATION_WINDOW_SLOT (l->data)) + { + return l->data; + } + } + + return NULL; +} + +/* We redirect some slots and close others */ +static gboolean +should_close_slot_with_mount (CajaWindow *window, + CajaWindowSlot *slot, + GMount *mount) +{ + if (CAJA_IS_SPATIAL_WINDOW (window)) + { + return TRUE; + } + return caja_navigation_window_slot_should_close_with_mount (CAJA_NAVIGATION_WINDOW_SLOT (slot), + mount); +} + +/* Called whenever a mount is unmounted. Check and see if there are + * any windows open displaying contents on the mount. If there are, + * close them. It would also be cool to save open window and position + * info. + * + * This is also called on pre_unmount. + */ +static void +mount_removed_callback (GVolumeMonitor *monitor, + GMount *mount, + CajaApplication *application) +{ + GList *window_list, *node, *close_list; + CajaWindow *window; + CajaWindowSlot *slot; + CajaWindowSlot *force_no_close_slot; + GFile *root, *computer; + gboolean unclosed_slot; + + close_list = NULL; + force_no_close_slot = NULL; + unclosed_slot = FALSE; + + /* Check and see if any of the open windows are displaying contents from the unmounted mount */ + window_list = caja_application_get_window_list (); + + root = g_mount_get_root (mount); + /* Construct a list of windows to be closed. Do not add the non-closable windows to the list. */ + for (node = window_list; node != NULL; node = node->next) + { + window = CAJA_WINDOW (node->data); + if (window != NULL && window_can_be_closed (window)) + { + GList *l; + GList *lp; + GFile *location; + + for (lp = window->details->panes; lp != NULL; lp = lp->next) + { + CajaWindowPane *pane; + pane = (CajaWindowPane*) lp->data; + for (l = pane->slots; l != NULL; l = l->next) + { + slot = l->data; + location = slot->location; + if (g_file_has_prefix (location, root) || + g_file_equal (location, root)) + { + close_list = g_list_prepend (close_list, slot); + + if (!should_close_slot_with_mount (window, slot, mount)) + { + /* We'll be redirecting this, not closing */ + unclosed_slot = TRUE; + } + } + else + { + unclosed_slot = TRUE; + } + } /* for all slots */ + } /* for all panes */ + } + } + + if (caja_application_desktop_windows == NULL && + !unclosed_slot) + { + /* We are trying to close all open slots. Keep one navigation slot open. */ + force_no_close_slot = get_first_navigation_slot (close_list); + } + + /* Handle the windows in the close list. */ + for (node = close_list; node != NULL; node = node->next) + { + slot = node->data; + window = slot->pane->window; + + if (should_close_slot_with_mount (window, slot, mount) && + slot != force_no_close_slot) + { + caja_window_slot_close (slot); + } + else + { + computer = g_file_new_for_uri ("computer:///"); + caja_window_slot_go_to (slot, computer, FALSE); + g_object_unref(computer); + } + } + + g_list_free (close_list); +} + +static char * +icon_to_string (GIcon *icon) +{ + const char * const *names; + GFile *file; + + if (icon == NULL) + { + return NULL; + } + else if (G_IS_THEMED_ICON (icon)) + { + names = g_themed_icon_get_names (G_THEMED_ICON (icon)); + return g_strjoinv (":", (char **)names); + } + else if (G_IS_FILE_ICON (icon)) + { + file = g_file_icon_get_file (G_FILE_ICON (icon)); + return g_file_get_path (file); + } + return NULL; +} + +static GIcon * +icon_from_string (const char *string) +{ + GFile *file; + GIcon *icon; + gchar **names; + + if (g_path_is_absolute (string)) + { + file = g_file_new_for_path (string); + icon = g_file_icon_new (file); + g_object_unref (file); + return icon; + } + else + { + names = g_strsplit (string, ":", 0); + icon = g_themed_icon_new_from_names (names, -1); + g_strfreev (names); + return icon; + } + return NULL; +} + +static char * +caja_application_get_session_data (void) +{ + xmlDocPtr doc; + xmlNodePtr root_node, history_node; + GList *l; + char *data; + unsigned n_processed; + xmlSaveCtxtPtr ctx; + xmlBufferPtr buffer; + + doc = xmlNewDoc ("1.0"); + + root_node = xmlNewNode (NULL, "session"); + xmlDocSetRootElement (doc, root_node); + + history_node = xmlNewChild (root_node, NULL, "history", NULL); + + n_processed = 0; + for (l = caja_get_history_list (); l != NULL; l = l->next) + { + CajaBookmark *bookmark; + xmlNodePtr bookmark_node; + GIcon *icon; + char *tmp; + + bookmark = l->data; + + bookmark_node = xmlNewChild (history_node, NULL, "bookmark", NULL); + + tmp = caja_bookmark_get_name (bookmark); + xmlNewProp (bookmark_node, "name", tmp); + g_free (tmp); + + icon = caja_bookmark_get_icon (bookmark); + tmp = icon_to_string (icon); + g_object_unref (icon); + if (tmp) + { + xmlNewProp (bookmark_node, "icon", tmp); + g_free (tmp); + } + + tmp = caja_bookmark_get_uri (bookmark); + xmlNewProp (bookmark_node, "uri", tmp); + g_free (tmp); + + if (caja_bookmark_get_has_custom_name (bookmark)) + { + xmlNewProp (bookmark_node, "has_custom_name", "TRUE"); + } + + if (++n_processed > 50) /* prevent history list from growing arbitrarily large. */ + { + break; + } + } + + for (l = caja_application_window_list; l != NULL; l = l->next) + { + xmlNodePtr win_node, slot_node; + CajaWindow *window; + CajaWindowSlot *slot, *active_slot; + GList *slots, *m; + char *tmp; + + window = l->data; + + win_node = xmlNewChild (root_node, NULL, "window", NULL); + + xmlNewProp (win_node, "type", CAJA_IS_NAVIGATION_WINDOW (window) ? "navigation" : "spatial"); + + if (CAJA_IS_NAVIGATION_WINDOW (window)) /* spatial windows store their state as file metadata */ + { + GdkWindow *gdk_window; + + tmp = eel_gtk_window_get_geometry_string (GTK_WINDOW (window)); + xmlNewProp (win_node, "geometry", tmp); + g_free (tmp); + + gdk_window = gtk_widget_get_window (GTK_WIDGET (window)); + + if (gdk_window && + gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_MAXIMIZED) + { + xmlNewProp (win_node, "maximized", "TRUE"); + } + + if (gdk_window && + gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_STICKY) + { + xmlNewProp (win_node, "sticky", "TRUE"); + } + + if (gdk_window && + gdk_window_get_state (gdk_window) & GDK_WINDOW_STATE_ABOVE) + { + xmlNewProp (win_node, "keep-above", "TRUE"); + } + } + + slots = caja_window_get_slots (window); + active_slot = caja_window_get_active_slot (window); + + /* store one slot as window location. Otherwise + * older Caja versions will bail when reading the file. */ + tmp = caja_window_slot_get_location_uri (active_slot); + xmlNewProp (win_node, "location", tmp); + g_free (tmp); + + for (m = slots; m != NULL; m = m->next) + { + slot = CAJA_WINDOW_SLOT (m->data); + + slot_node = xmlNewChild (win_node, NULL, "slot", NULL); + + tmp = caja_window_slot_get_location_uri (slot); + xmlNewProp (slot_node, "location", tmp); + g_free (tmp); + + if (slot == active_slot) + { + xmlNewProp (slot_node, "active", "TRUE"); + } + } + + g_list_free (slots); + } + + buffer = xmlBufferCreate (); + xmlIndentTreeOutput = 1; + ctx = xmlSaveToBuffer (buffer, "UTF-8", XML_SAVE_FORMAT); + if (xmlSaveDoc (ctx, doc) < 0 || + xmlSaveFlush (ctx) < 0) + { + g_message ("failed to save session"); + } + + xmlSaveClose(ctx); + data = g_strndup (buffer->content, buffer->use); + xmlBufferFree (buffer); + + xmlFreeDoc (doc); + + return data; +} + +void +caja_application_load_session (CajaApplication *application) +{ + xmlDocPtr doc; + gboolean bail; + xmlNodePtr root_node; + GKeyFile *state_file; + char *data; + + if (!egg_sm_client_is_resumed (application->smclient)) + { + return; + } + + state_file = egg_sm_client_get_state_file (application->smclient); + if (!state_file) + { + return; + } + + data = g_key_file_get_string (state_file, + "Caja", + "documents", + NULL); + if (data == NULL) + { + return; + } + + bail = TRUE; + + doc = xmlReadMemory (data, strlen (data), NULL, "UTF-8", 0); + if (doc != NULL && (root_node = xmlDocGetRootElement (doc)) != NULL) + { + xmlNodePtr node; + + bail = FALSE; + + for (node = root_node->children; node != NULL; node = node->next) + { + + if (!strcmp (node->name, "text")) + { + continue; + } + else if (!strcmp (node->name, "history")) + { + xmlNodePtr bookmark_node; + gboolean emit_change; + + emit_change = FALSE; + + for (bookmark_node = node->children; bookmark_node != NULL; bookmark_node = bookmark_node->next) + { + if (!strcmp (bookmark_node->name, "text")) + { + continue; + } + else if (!strcmp (bookmark_node->name, "bookmark")) + { + xmlChar *name, *icon_str, *uri; + gboolean has_custom_name; + GIcon *icon; + GFile *location; + + uri = xmlGetProp (bookmark_node, "uri"); + name = xmlGetProp (bookmark_node, "name"); + has_custom_name = xmlHasProp (bookmark_node, "has_custom_name") ? TRUE : FALSE; + icon_str = xmlGetProp (bookmark_node, "icon"); + icon = NULL; + if (icon_str) + { + icon = icon_from_string (icon_str); + } + location = g_file_new_for_uri (uri); + + emit_change |= caja_add_to_history_list_no_notify (location, name, has_custom_name, icon); + + g_object_unref (location); + + if (icon) + { + g_object_unref (icon); + } + xmlFree (name); + xmlFree (uri); + xmlFree (icon_str); + } + else + { + g_message ("unexpected bookmark node %s while parsing session data", bookmark_node->name); + bail = TRUE; + continue; + } + } + + if (emit_change) + { + caja_send_history_list_changed (); + } + } + else if (!strcmp (node->name, "window")) + { + CajaWindow *window; + xmlChar *type, *location_uri, *slot_uri; + xmlNodePtr slot_node; + GFile *location; + int i; + + type = xmlGetProp (node, "type"); + if (type == NULL) + { + g_message ("empty type node while parsing session data"); + bail = TRUE; + continue; + } + + location_uri = xmlGetProp (node, "location"); + if (location_uri == NULL) + { + g_message ("empty location node while parsing session data"); + bail = TRUE; + xmlFree (type); + continue; + } + + if (!strcmp (type, "navigation")) + { + xmlChar *geometry; + + window = caja_application_create_navigation_window (application, NULL, gdk_screen_get_default ()); + + geometry = xmlGetProp (node, "geometry"); + if (geometry != NULL) + { + eel_gtk_window_set_initial_geometry_from_string + (GTK_WINDOW (window), + geometry, + CAJA_NAVIGATION_WINDOW_MIN_WIDTH, + CAJA_NAVIGATION_WINDOW_MIN_HEIGHT, + FALSE); + } + xmlFree (geometry); + + if (xmlHasProp (node, "maximized")) + { + gtk_window_maximize (GTK_WINDOW (window)); + } + else + { + gtk_window_unmaximize (GTK_WINDOW (window)); + } + + if (xmlHasProp (node, "sticky")) + { + gtk_window_stick (GTK_WINDOW (window)); + } + else + { + gtk_window_unstick (GTK_WINDOW (window)); + } + + if (xmlHasProp (node, "keep-above")) + { + gtk_window_set_keep_above (GTK_WINDOW (window), TRUE); + } + else + { + gtk_window_set_keep_above (GTK_WINDOW (window), FALSE); + } + + for (i = 0, slot_node = node->children; slot_node != NULL; slot_node = slot_node->next) + { + if (!strcmp (slot_node->name, "slot")) + { + slot_uri = xmlGetProp (slot_node, "location"); + if (slot_uri != NULL) + { + CajaWindowSlot *slot; + + if (i == 0) + { + slot = window->details->active_pane->active_slot; + } + else + { + slot = caja_window_open_slot (window->details->active_pane, CAJA_WINDOW_OPEN_SLOT_APPEND); + } + + location = g_file_new_for_uri (slot_uri); + caja_window_slot_open_location (slot, location, FALSE); + + if (xmlHasProp (slot_node, "active")) + { + caja_window_set_active_slot (slot->pane->window, slot); + } + + i++; + } + xmlFree (slot_uri); + } + } + + if (i == 0) + { + /* This may be an old session file */ + location = g_file_new_for_uri (location_uri); + caja_window_slot_open_location (window->details->active_pane->active_slot, location, FALSE); + g_object_unref (location); + } + } + else if (!strcmp (type, "spatial")) + { + location = g_file_new_for_uri (location_uri); + window = caja_application_present_spatial_window (application, NULL, NULL, location, gdk_screen_get_default ()); + g_object_unref (location); + } + else + { + g_message ("unknown window type \"%s\" while parsing session data", type); + bail = TRUE; + } + + xmlFree (type); + xmlFree (location_uri); + } + else + { + g_message ("unexpected node %s while parsing session data", node->name); + bail = TRUE; + continue; + } + } + } + + if (doc != NULL) + { + xmlFreeDoc (doc); + } + + g_free (data); + + if (bail) + { + g_message ("failed to load session"); + } +} + +static void +caja_application_class_init (CajaApplicationClass *class) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (class); + object_class->finalize = caja_application_finalize; +} |