/* * Copyright (C) 2002 Jorn Baayen * * This program 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, 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. * * $Id$ */ #ifdef HAVE_CONFIG_H #include #endif #include "session.h" #include "ephy-shell.h" #include "ephy-tab.h" #include "ephy-window.h" #include "ephy-history-window.h" #include "ephy-bookmarks-editor.h" #include "ephy-string.h" #include "ephy-file-helpers.h" #include "eel-gconf-extensions.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static void session_class_init (SessionClass *klass); static void session_init (Session *t); static void session_finalize (GObject *object); static void session_dispose (GObject *object); static GObjectClass *parent_class = NULL; #define BOOKMARKS_EDITOR_ID "BookmarksEditor" #define HISTORY_WINDOW_ID "HistoryWindow" #define EPHY_SESSION_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SESSION, SessionPrivate)) struct SessionPrivate { GList *windows; }; enum { NEW_WINDOW, CLOSE_WINDOW, LAST_SIGNAL }; static guint session_signals[LAST_SIGNAL] = { 0 }; GType session_get_type (void) { static GType session_type = 0; if (session_type == 0) { static const GTypeInfo our_info = { sizeof (SessionClass), NULL, /* base_init */ NULL, /* base_finalize */ (GClassInitFunc) session_class_init, NULL, NULL, /* class_data */ sizeof (Session), 0, /* n_preallocs */ (GInstanceInitFunc) session_init }; session_type = g_type_register_static (G_TYPE_OBJECT, "Session", &our_info, 0); } return session_type; } static void session_class_init (SessionClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); parent_class = g_type_class_peek_parent (klass); object_class->finalize = session_finalize; object_class->dispose = session_dispose; session_signals[NEW_WINDOW] = g_signal_new ("new_window", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SessionClass, new_window), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); session_signals[CLOSE_WINDOW] = g_signal_new ("close_window", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (SessionClass, close_window), NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); g_type_class_add_private (object_class, sizeof(SessionPrivate)); } static char * get_session_filename (const char *filename) { char *save_to; g_return_val_if_fail (filename != NULL, NULL); if (strcmp (filename, SESSION_CRASHED) == 0) { save_to = g_build_filename (ephy_dot_dir (), "session_crashed.xml", NULL); } else if (strcmp (filename, SESSION_GNOME) == 0) { char *tmp; save_to = g_build_filename (ephy_dot_dir (), "session_gnome-XXXXXX", NULL); tmp = ephy_file_tmp_filename (save_to, "xml"); g_free (save_to); save_to = tmp; } else { save_to = g_strdup (filename); } return save_to; } static void do_session_resume (Session *session) { char *crashed_session; crashed_session = get_session_filename (SESSION_CRASHED); session_load (session, crashed_session); g_free (crashed_session); } static void crashed_resume_dialog (Session *session) { GtkWidget *dialog; GtkWidget *label; GtkWidget *vbox; GtkWidget *hbox; GtkWidget *image; char *str; dialog = gtk_dialog_new_with_buttons (_("Crash Recovery"), NULL, GTK_DIALOG_NO_SEPARATOR, _("_Don't Recover"), GTK_RESPONSE_CANCEL, _("_Recover"), GTK_RESPONSE_OK, NULL); gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); gtk_container_set_border_width (GTK_CONTAINER (dialog), 5); gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 14); hbox = gtk_hbox_new (FALSE, 6); gtk_container_set_border_width (GTK_CONTAINER (GTK_BOX (hbox)), 5); gtk_widget_show (hbox); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), hbox, TRUE, TRUE, 0); image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_DIALOG); gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0); gtk_widget_show (image); gtk_box_pack_start (GTK_BOX (hbox), image, TRUE, TRUE, 0); vbox = gtk_vbox_new (FALSE, 6); gtk_widget_show (vbox); gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); label = gtk_label_new (NULL); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); gtk_widget_show (label); str = g_strconcat ("", _("Epiphany appears to have crashed or been killed the last time it was run."), "", NULL); gtk_label_set_markup (GTK_LABEL (label), str); gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); g_free (str); label = gtk_label_new (_("You can recover the opened tabs and windows.")); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_widget_show (label); gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) { do_session_resume (session); } gtk_widget_destroy (dialog); } /** * session_autoresume: * @session: a #Session * * Resume a crashed session when necessary (interactive) * * Return value: TRUE if at least a window has been opened **/ gboolean session_autoresume (Session *session) { char *saved_session; if (session->priv->windows != NULL) return FALSE; saved_session = get_session_filename (SESSION_CRASHED); if (g_file_test (saved_session, G_FILE_TEST_EXISTS)) { crashed_resume_dialog (session); } g_free (saved_session); return (session->priv->windows != NULL); } static void create_session_directory (void) { char *dir; dir = g_build_filename (ephy_dot_dir (), "/sessions", NULL); if (!g_file_test (dir, G_FILE_TEST_EXISTS)) { if (mkdir (dir, 488) != 0) { g_error ("couldn't make %s' directory", dir); } } g_free (dir); } static void session_close (Session *session) { GList *windows; /* close all windows */ windows = g_list_copy (session->priv->windows); g_list_foreach (windows, (GFunc)gtk_widget_destroy, NULL); g_list_free (windows); } static void session_delete (Session *session, const char *filename) { char *save_to; save_to = get_session_filename (filename); gnome_vfs_unlink (save_to); g_free (save_to); } static void session_dispose (GObject *object) { Session *session = EPHY_SESSION(object); session_delete (session, SESSION_CRASHED); } static void session_finalize (GObject *object) { Session *t = EPHY_SESSION (object); g_list_free (t->priv->windows); G_OBJECT_CLASS (parent_class)->finalize (object); } /** * session_new: * * Create a #Session. A session hold the information * about the windows currently opened and is able to persist * and restore his status. **/ Session * session_new (void) { return EPHY_SESSION (g_object_new (EPHY_TYPE_SESSION, NULL)); } static void save_tab (EphyWindow *window, EphyTab *tab, xmlDocPtr doc, xmlNodePtr window_node) { EmbedChromeMask chrome; char *location; xmlNodePtr embed_node; EphyEmbed *embed; chrome = ephy_window_get_chrome (window); /* skip if it's a XUL dialog */ if (chrome & EMBED_CHROME_OPENASCHROME) return; /* make a new XML node */ embed_node = xmlNewDocNode (doc, NULL, "embed", NULL); /* otherwise, use the actual location. */ embed = ephy_tab_get_embed (tab); location = ephy_embed_get_location (embed, TRUE); xmlSetProp (embed_node, "url", location); g_free (location); /* insert node into the tree */ xmlAddChild (window_node, embed_node); } static void save_window_geometry (GtkWindow *window, xmlNodePtr window_node) { int x = 0, y = 0, width = 0, height = 0; gchar buffer[32]; /* get window geometry */ gtk_window_get_size (window, &width, &height); gtk_window_get_position (window, &x, &y); /* set window properties */ g_snprintf(buffer, 32, "%d", x); xmlSetProp (window_node, "x", buffer); g_snprintf(buffer, 32, "%d", y); xmlSetProp (window_node, "y", buffer); g_snprintf(buffer, 32, "%d", width); xmlSetProp (window_node, "width", buffer); g_snprintf(buffer, 32, "%d", height); xmlSetProp (window_node, "height", buffer); } static void save_tool_window (GtkWindow *window, xmlDocPtr doc, xmlNodePtr root_node, const char *id) { xmlNodePtr window_node; window_node = xmlNewDocNode (doc, NULL, "toolwindow", NULL); xmlSetProp (window_node, "id", id); save_window_geometry (window, window_node); xmlAddChild (root_node, window_node); } static void save_ephy_window (EphyWindow *window, xmlDocPtr doc, xmlNodePtr root_node) { GList *tabs, *l; xmlNodePtr window_node; tabs = ephy_window_get_tabs (window); /* Do not save empty EphyWindow */ if (tabs == NULL) return; window_node = xmlNewDocNode (doc, NULL, "window", NULL); save_window_geometry (GTK_WINDOW (window), window_node); for (l = tabs; l != NULL; l = l->next) { EphyTab *tab = EPHY_TAB(l->data); save_tab (window, tab, doc, window_node); } g_list_free (tabs); xmlAddChild (root_node, window_node); } static void session_save (Session *session, const char *filename) { GList *w; xmlNodePtr root_node; xmlDocPtr doc; char *save_to; if (session->priv->windows == NULL) { session_delete (session, filename); return; } save_to = get_session_filename (filename); doc = xmlNewDoc ("1.0"); /* create and set the root node for the session */ root_node = xmlNewDocNode (doc, NULL, "session", NULL); xmlDocSetRootElement (doc, root_node); /* iterate through all the windows */ for (w = session->priv->windows; w != NULL; w = w->next) { GtkWindow *window = w->data; if (EPHY_IS_WINDOW (window)) { save_ephy_window (EPHY_WINDOW (window), doc, root_node); } else if (EPHY_IS_BOOKMARKS_EDITOR (window)) { save_tool_window (window, doc, root_node, BOOKMARKS_EDITOR_ID); } else if (EPHY_IS_HISTORY_WINDOW (window)) { save_tool_window (window, doc, root_node, HISTORY_WINDOW_ID); } } /* save it all out to disk */ xmlSaveFile (save_to, doc); xmlFreeDoc (doc); g_free (save_to); } static void parse_embed (xmlNodePtr child, EphyWindow *window) { while (child != NULL) { if (strcmp (child->name, "embed") == 0) { xmlChar *url; g_return_if_fail (window != NULL); url = xmlGetProp (child, "url"); ephy_shell_new_tab (ephy_shell, window, NULL, url, EPHY_NEW_TAB_IN_EXISTING_WINDOW | EPHY_NEW_TAB_OPEN_PAGE | EPHY_NEW_TAB_APPEND_LAST); xmlFree (url); } child = child->next; } } /* * session_load: * @session: a #Session * @filename: the path of the source file * * Load a session from disk, restoring the windows and their state **/ void session_load (Session *session, const char *filename) { xmlDocPtr doc; xmlNodePtr child; GtkWidget *widget = NULL; char *path; path = get_session_filename (filename); doc = xmlParseFile (filename); g_free (path); if (doc == NULL) return; child = xmlDocGetRootElement (doc); /* skip the session node */ child = child->children; while (child != NULL) { if (xmlStrEqual (child->name, "window")) { widget = GTK_WIDGET (ephy_window_new ()); parse_embed (child->children, EPHY_WINDOW (widget)); } else if (xmlStrEqual (child->name, "toolwindow")) { xmlChar *id; id = xmlGetProp (child, "id"); if (id && xmlStrEqual (BOOKMARKS_EDITOR_ID, id)) { widget = ephy_shell_get_bookmarks_editor (ephy_shell); } else if (id && xmlStrEqual (HISTORY_WINDOW_ID, id)) { widget = ephy_shell_get_history_window (ephy_shell); } } if (widget) { xmlChar *tmp; gulong x = 0, y = 0, width = 0, height = 0; tmp = xmlGetProp (child, "x"); ephy_string_to_int (tmp, &x); xmlFree (tmp); tmp = xmlGetProp (child, "y"); ephy_string_to_int (tmp, &y); xmlFree (tmp); tmp = xmlGetProp (child, "width"); ephy_string_to_int (tmp, &width); xmlFree (tmp); tmp = xmlGetProp (child, "height"); ephy_string_to_int (tmp, &height); xmlFree (tmp); gtk_window_move (GTK_WINDOW (widget), x, y); gtk_window_set_default_size (GTK_WINDOW (widget), width, height); gtk_widget_show (widget); } child = child->next; } xmlFreeDoc (doc); } GList * session_get_windows (Session *session) { g_return_val_if_fail (EPHY_IS_SESSION (session), NULL); return g_list_copy (session->priv->windows); } static void net_stop_cb (EphyEmbed *embed, Session *session) { session_save (session, SESSION_CRASHED); } static void tab_added_cb (GtkWidget *nb, GtkWidget *child, Session *session) { g_signal_connect (child, "net_stop", G_CALLBACK (net_stop_cb), session); } static void tab_removed_cb (GtkWidget *nb, GtkWidget *child, Session *session) { session_save (session, SESSION_CRASHED); g_signal_handlers_disconnect_by_func (child, G_CALLBACK (net_stop_cb), session); } static void tabs_reordered_cb (GtkWidget *nb, Session *session) { session_save (session, SESSION_CRASHED); } /** * session_add_window: * @session: a #Session * @window: a #EphyWindow * * Add a window to the session. #EphyWindow take care of adding * itself to session. **/ void session_add_window (Session *session, GtkWindow *window) { session->priv->windows = g_list_append (session->priv->windows, window); if (EPHY_IS_WINDOW (window)) { GtkWidget *nb; nb = ephy_window_get_notebook (EPHY_WINDOW (window)); g_signal_connect (nb, "tab_added", G_CALLBACK (tab_added_cb), session); g_signal_connect (nb, "tab_removed", G_CALLBACK (tab_removed_cb), session); g_signal_connect (nb, "tabs_reordered", G_CALLBACK (tabs_reordered_cb), session); /* FIXME do not break the extensions until we figure out better api */ g_signal_emit (G_OBJECT (session), session_signals[NEW_WINDOW], 0, window); } session_save (session, SESSION_CRASHED); } /** * session_remove_window: * @session: a #Session * @window: a #EphyWindow * * Remove a window from the session. #EphyWindow take care of removing * itself to session. **/ void session_remove_window (Session *session, GtkWindow *window) { session->priv->windows = g_list_remove (session->priv->windows, window); if (EPHY_IS_WINDOW (window)) { g_object_ref (window); /* FIXME do not break the extensions until we figure out better api */ g_signal_emit (G_OBJECT (session), session_signals[CLOSE_WINDOW], 0, window); g_object_unref (window); } session_save (session, SESSION_CRASHED); } static gboolean save_yourself_cb (GnomeClient *client, gint phase, GnomeSaveStyle save_style, gboolean shutdown, GnomeInteractStyle interact_style, gboolean fast, Session *session) { char *argv[] = { "epiphany", "--load-session", NULL }; char *discard_argv[] = { "rm", "-r", NULL }; argv[2] = get_session_filename (SESSION_GNOME); gnome_client_set_restart_command (client, 3, argv); discard_argv[2] = argv[2]; gnome_client_set_discard_command (client, 3, discard_argv); session_save (session, argv[2]); g_free (argv[2]); return TRUE; } static void session_die_cb (GnomeClient* client, Session *session) { session_close (session); } static void gnome_session_init (Session *session) { GnomeClient *client; client = gnome_master_client (); g_signal_connect (G_OBJECT (client), "save_yourself", G_CALLBACK (save_yourself_cb), session); g_signal_connect (G_OBJECT (client), "die", G_CALLBACK (session_die_cb), session); } static void session_init (Session *session) { session->priv = EPHY_SESSION_GET_PRIVATE (session); session->priv->windows = NULL; create_session_directory (); gnome_session_init (session); }