From 10eab2350036e1c36377c93d31472125b3e7c8c7 Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Fri, 10 Jul 2009 18:36:49 -0400 Subject: Implement the shutdown protocol and stub in session management. The shutdown protocol is modelled after online/offline preparation. Session management code is copied from libegg. Not yet used. --- shell/Makefile.am | 2 +- shell/e-shell-backend.c | 61 ------------------ shell/e-shell-backend.h | 12 ---- shell/e-shell.c | 160 ++++++++++++++++++++++++++++-------------------- shell/e-shell.h | 4 +- shell/main.c | 142 +++--------------------------------------- 6 files changed, 107 insertions(+), 274 deletions(-) (limited to 'shell') diff --git a/shell/Makefile.am b/shell/Makefile.am index 8df302be77..878642ec21 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -3,7 +3,6 @@ SUBDIRS = . test endif AM_CPPFLAGS = \ - -I$(top_srcdir)/e-util \ -I$(top_srcdir)/widgets \ -I$(top_srcdir)/widgets/menus \ -I$(top_srcdir)/widgets/misc \ @@ -99,6 +98,7 @@ libeshell_la_LDFLAGS = $(NO_UNDEFINED) libeshell_la_LIBADD = \ $(top_builddir)/e-util/libeutil.la \ $(top_builddir)/filter/libfilter.la \ + $(top_builddir)/smclient/libeggsmclient.la \ $(top_builddir)/widgets/misc/libemiscwidgets.la \ $(top_builddir)/widgets/menus/libmenus.la \ $(SHELL_LIBS) diff --git a/shell/e-shell-backend.c b/shell/e-shell-backend.c index 9ee2edb4dc..c996d8ab01 100644 --- a/shell/e-shell-backend.c +++ b/shell/e-shell-backend.c @@ -369,67 +369,6 @@ e_shell_backend_start (EShellBackend *shell_backend) shell_backend->priv->started = TRUE; } -/** - * e_shell_backend_is_busy: - * @shell_backend: an #EShellBackend - * - * Returns %TRUE if @shell_backend is busy and cannot be shutdown at - * present. Each backend must define what "busy" means to them and - * determine an appropriate response. - * - * XXX This function is likely to change or disappear. I'm toying with - * the idea of just having it check whether there are any unfinished - * #EActivity's left, so we have a consistent and easily - * testable definition of what "busy" means. - * - * Returns: %TRUE if the backend is busy - **/ -gboolean -e_shell_backend_is_busy (EShellBackend *shell_backend) -{ - EShellBackendClass *class; - - g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), FALSE); - - class = E_SHELL_BACKEND_GET_CLASS (shell_backend); - - if (class->is_busy == NULL) - return FALSE; - - return class->is_busy (shell_backend); -} - -/** - * e_shell_backend_shutdown: - * @shell_backend: an #EShellBackend - * - * Alerts @shell_backend to begin shutdown procedures. If the backend is - * busy and cannot immediately shut down, the function returns %FALSE. - * A %TRUE response implies @shell_backend has successfully shut down. - * - * XXX This function is likely to change or disappear. I'm toying with - * the idea of just having it check whether there are any unfinished - * #EActivity's left, so we have a consistent and easily - * testable definition of what "busy" means. - * - * Returns: %TRUE if the backend has shut down, %FALSE if the backend is - * busy and cannot immediately shut down - */ -gboolean -e_shell_backend_shutdown (EShellBackend *shell_backend) -{ - EShellBackendClass *class; - - g_return_val_if_fail (E_IS_SHELL_BACKEND (shell_backend), TRUE); - - class = E_SHELL_BACKEND_GET_CLASS (shell_backend); - - if (class->shutdown == NULL) - return TRUE; - - return class->shutdown (shell_backend); -} - /** * e_shell_migrate: * @shell_backend: an #EShellBackend diff --git a/shell/e-shell-backend.h b/shell/e-shell-backend.h index 0d4e02f9bb..8b7e24034f 100644 --- a/shell/e-shell-backend.h +++ b/shell/e-shell-backend.h @@ -91,14 +91,6 @@ struct _EShellBackend { * corresponding #EShellView subclass. It allows the * backend to delay initialization steps that consume * significant resources until they are actually needed. - * @is_busy: Method for querying whether the backend has operations - * in progress that cannot be cancelled or finished - * immediately. Returning %TRUE prevents the application - * from shutting down. - * @shutdown: Method for notifying the backend to begin shutting - * down. Returning %FALSE indicates there are still - * unfinished operations and the #EShell should check - * back shortly. * @migrate: Method for notifying the backend to migrate data and * settings from the given version. Returns %TRUE if the * migration was successful or if no action was necessary. @@ -119,8 +111,6 @@ struct _EShellBackendClass { /* Methods */ void (*start) (EShellBackend *shell_backend); - gboolean (*is_busy) (EShellBackend *shell_backend); - gboolean (*shutdown) (EShellBackend *shell_backend); gboolean (*migrate) (EShellBackend *shell_backend, gint major, gint minor, @@ -138,8 +128,6 @@ struct _EShell *e_shell_backend_get_shell (EShellBackend *shell_backend); void e_shell_backend_add_activity (EShellBackend *shell_backend, EActivity *activity); void e_shell_backend_start (EShellBackend *shell_backend); -gboolean e_shell_backend_is_busy (EShellBackend *shell_backend); -gboolean e_shell_backend_shutdown (EShellBackend *shell_backend); gboolean e_shell_backend_migrate (EShellBackend *shell_backend, gint major, gint minor, diff --git a/shell/e-shell.c b/shell/e-shell.c index c9e8d46317..35908e0855 100644 --- a/shell/e-shell.c +++ b/shell/e-shell.c @@ -50,6 +50,7 @@ struct _EShellPrivate { GHashTable *backends_by_scheme; gpointer preparing_for_line_change; /* weak pointer */ + gpointer preparing_for_shutdown; /* weak pointer */ guint auto_reconnect : 1; guint network_available : 1; @@ -69,6 +70,7 @@ enum { HANDLE_URI, PREPARE_FOR_OFFLINE, PREPARE_FOR_ONLINE, + PREPARE_FOR_SHUTDOWN, SEND_RECEIVE, WINDOW_CREATED, WINDOW_DESTROYED, @@ -122,7 +124,9 @@ shell_window_delete_event_cb (EShell *shell, return FALSE; /* Otherwise we initiate application shutdown. */ - return !e_shell_quit (shell); + e_shell_quit (shell); + + return TRUE; } static gboolean @@ -271,6 +275,40 @@ shell_prepare_for_online (EShell *shell) g_object_unref (shell->priv->preparing_for_line_change); } +static void +shell_ready_for_shutdown (EShell *shell, + EActivity *activity, + gboolean is_last_ref) +{ + GList *list; + + if (!is_last_ref) + return; + + /* Increment the reference count so we can safely emit + * a signal without triggering the toggle reference. */ + g_object_ref (activity); + + e_activity_complete (activity); + + g_object_remove_toggle_ref ( + G_OBJECT (activity), (GToggleNotify) + shell_ready_for_shutdown, shell); + + /* Finalize the activity. */ + g_object_ref (activity); + + g_message ("Shutdown preparations complete."); + + /* Destroy all watched windows. Note, we iterate over a -copy- + * of the watched windows list because the act of destroying a + * watched window will modify the watched windows list, which + * would derail the iteration. */ + list = g_list_copy (e_shell_get_watched_windows (shell)); + g_list_foreach (list, (GFunc) gtk_widget_destroy, NULL); + g_list_free (list); +} + static void shell_load_modules (EShell *shell) { @@ -358,54 +396,6 @@ shell_create_backends (EShell *shell) g_free (children); } -static gboolean -shell_shutdown_timeout (EShell *shell) -{ - GList *list, *iter; - gboolean proceed = TRUE; - static guint source_id = 0; - static guint message_timer = 1; - - /* Module list is read-only; do not free. */ - list = e_shell_get_shell_backends (shell); - - /* Any backend can defer shutdown if it's still busy. */ - for (iter = list; proceed && iter != NULL; iter = iter->next) { - EShellBackend *shell_backend = iter->data; - proceed = e_shell_backend_shutdown (shell_backend); - - /* Emit a message every few seconds to indicate - * which backend(s) we're still waiting on. */ - if (proceed || message_timer == 0) - continue; - - g_message ( - _("Waiting for the \"%s\" backend to finish..."), - E_SHELL_BACKEND_GET_CLASS (shell_backend)->name); - } - - message_timer = (message_timer + 1) % 10; - - /* If we're go for shutdown, destroy all shell windows. Note, - * we iterate over a /copy/ of the active windows list because - * the act of destroying a shell window will modify the active - * windows list, which would otherwise derail the iteration. */ - if (proceed) { - list = g_list_copy (shell->priv->watched_windows); - g_list_foreach (list, (GFunc) gtk_widget_destroy, NULL); - g_list_free (list); - - /* If a backend is still busy, try again after a short delay. */ - } else if (source_id == 0) - source_id = g_timeout_add ( - SHUTDOWN_TIMEOUT, (GSourceFunc) - shell_shutdown_timeout, shell); - - /* Return TRUE to repeat the timeout, FALSE to stop it. This - * may seem backwards if the function was called directly. */ - return !proceed; -} - static void shell_set_property (GObject *object, guint property_id, @@ -760,6 +750,31 @@ shell_class_init (EShellClass *class) G_TYPE_NONE, 1, E_TYPE_ACTIVITY); + /** + * EShell::prepare-for-shutdown + * @shell: the #EShell which emitted the signal + * @activity: the #EActivity for shutdown preparations + * + * Emitted when the user elects to quit the application. An + * #EShellBackend should listen for this signal and make + * preparations for shutting down. + * + * If preparations for shutting down cannot immediately be completed + * (such as when there are uncompleted network operations), the + * #EShellBackend should reference the @activity until preparations + * are complete, and then unreference the @activity. This will + * delay Evolution from actually shutting down until all backends + * have unreferenced @activity. + **/ + signals[PREPARE_FOR_SHUTDOWN] = g_signal_new ( + "prepare-for-shutdown", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, + E_TYPE_ACTIVITY); + /** * EShell::send-receive * @shell: the #EShell which emitted the signal @@ -1411,23 +1426,38 @@ e_shell_event (EShell *shell, g_signal_emit (shell, signals[EVENT], detail, event_data); } -gboolean -e_shell_is_busy (EShell *shell) +void +e_shell_quit (EShell *shell) { - /* FIXME */ - return FALSE; -} + GList *list, *iter; -gboolean -e_shell_do_quit (EShell *shell) -{ - /* FIXME */ - return TRUE; -} + g_return_if_fail (E_IS_SHELL (shell)); -gboolean -e_shell_quit (EShell *shell) -{ - /* FIXME */ - return TRUE; + /* Are preparations already in progress? */ + if (shell->priv->preparing_for_shutdown != NULL) + return; + + g_message ("Preparing for shutdown..."); + + shell->priv->preparing_for_shutdown = + e_activity_new (_("Preparing to shut down...")); + + g_object_add_toggle_ref ( + G_OBJECT (shell->priv->preparing_for_shutdown), + (GToggleNotify) shell_ready_for_shutdown, shell); + + g_object_add_weak_pointer ( + G_OBJECT (shell->priv->preparing_for_shutdown), + &shell->priv->preparing_for_shutdown); + + g_signal_emit ( + shell, signals[PREPARE_FOR_SHUTDOWN], 0, + shell->priv->preparing_for_shutdown); + + g_object_unref (shell->priv->preparing_for_shutdown); + + /* Desensitize all watched windows to prevent user action. */ + list = e_shell_get_watched_windows (shell); + for (iter = list; iter != NULL; iter = iter->next) + gtk_widget_set_sensitive (GTK_WIDGET (iter->data), FALSE); } diff --git a/shell/e-shell.h b/shell/e-shell.h index 1d0b9ab705..3b414e641c 100644 --- a/shell/e-shell.h +++ b/shell/e-shell.h @@ -104,9 +104,7 @@ GtkWidget * e_shell_get_preferences_window (EShell *shell); void e_shell_event (EShell *shell, const gchar *event_name, gpointer event_data); -gboolean e_shell_is_busy (EShell *shell); -gboolean e_shell_do_quit (EShell *shell); -gboolean e_shell_quit (EShell *shell); +void e_shell_quit (EShell *shell); G_END_DECLS diff --git a/shell/main.c b/shell/main.c index b921dd87ec..bbd9319176 100644 --- a/shell/main.c +++ b/shell/main.c @@ -58,7 +58,6 @@ #include #include #include -#include #include @@ -427,8 +426,7 @@ static void set_paths (void) { /* Set PATH to include the Evolution executable's folder - * and the lib/evolution/$(BASE_VERSION)/components folder. - */ + * and the lib/evolution/$(BASE_VERSION)/components folder. */ wchar_t exe_filename[MAX_PATH]; wchar_t *p; gchar *exe_folder_utf8; @@ -449,18 +447,16 @@ set_paths (void) *p = L'\0'; top_folder_utf8 = g_utf16_to_utf8 (exe_filename, -1, NULL, NULL, NULL); - components_folder_utf8 = - g_strconcat (top_folder_utf8, - "/lib/evolution/" BASE_VERSION "/components", - NULL); - - path = g_build_path (";", - exe_folder_utf8, - components_folder_utf8, - g_getenv ("PATH"), - NULL); + components_folder_utf8 = g_strconcat ( + top_folder_utf8, "/lib/evolution/" + BASE_VERSION "/components", NULL); + + path = g_build_path ( + ";", exe_folder_utf8, + components_folder_utf8, g_getenv ("PATH"), NULL); if (!g_setenv ("PATH", path, TRUE)) - g_warning ("Could not set PATH for Evolution and its child processes"); + g_warning ("Could not set PATH for Evolution " + "and its child processes"); g_free (path); g_free (exe_folder_utf8); @@ -477,118 +473,15 @@ shell_window_destroyed_cb (EShell *shell) gtk_main_quit (); } -static gint -master_client_save_yourself_cb (GnomeClient *client, - GnomeSaveStyle save_style, - gint shutdown, - GnomeInteractStyle interact_style, - gint fast, - gpointer user_data) -{ - EShell *shell = user_data; - - return !e_shell_is_busy (shell); -} - -static void -master_client_die_cb (GnomeClient *client, - gpointer user_data) -{ - EShell *shell = user_data; - - e_shell_do_quit (shell); -} - -/* taken from nautilus */ -static guint32 -slowly_and_stupidly_obtain_timestamp (GdkDisplay *display) -{ - Display *xdisplay; - Window xwindow; - XEvent event; - XSetWindowAttributes attrs; - Atom atom_name; - Atom atom_type; - const gchar *name; - - xdisplay = GDK_DISPLAY_XDISPLAY (display); - - attrs.override_redirect = True; - attrs.event_mask = PropertyChangeMask | StructureNotifyMask; - - xwindow = XCreateWindow ( - xdisplay, RootWindow (xdisplay, 0), - -100, -100, 1, 1, - 0, - CopyFromParent, - CopyFromParent, - CopyFromParent, - CWOverrideRedirect | CWEventMask, - &attrs); - - atom_name = XInternAtom (xdisplay, "WM_NAME", TRUE); - g_assert (atom_name != None); - - atom_type = XInternAtom (xdisplay, "STRING", TRUE); - g_assert (atom_type != None); - - name = "Fake Window"; - XChangeProperty ( - xdisplay, xwindow, atom_name, atom_type, - 8, PropModeReplace, (guchar *) name, - strlen (name)); - - XWindowEvent ( - xdisplay, xwindow, PropertyChangeMask, &event); - - XDestroyWindow (xdisplay, xwindow); - - return event.xproperty.time; -} - -static gchar * -pick_startup_id (void) -{ - GdkDisplay *display; - const gchar *startup_id; - gchar *id; - - /* XXX This copies logic from unique_app_new(), which we can't use - * because we're subclassing UniqueApp. I already sent ebassi - * a patch to fix this. */ - - display = gdk_display_get_default (); - - /* Try and get the startup notification ID from GDK, the - * environment or, if everything else failed, fake one. */ - startup_id = gdk_x11_display_get_startup_notification_id (display); - - if (!startup_id || startup_id[0] == '\0') - startup_id = g_getenv ("DESKTOP_STARTUP_ID"); - - if (!startup_id || startup_id[0] == '\0') { - guint32 timestamp; - - timestamp = slowly_and_stupidly_obtain_timestamp (display); - id = g_strdup_printf ("_TIME%lu", (gulong) timestamp); - } else - id = g_strdup (startup_id); - - return id; -} - static void create_default_shell (void) { EShell *shell; GConfClient *conf_client; - GnomeClient *master_client; gboolean online = TRUE; - gchar *startup_id; GError *error = NULL; conf_client = gconf_client_get_default (); - master_client = gnome_master_client (); if (start_online) online = TRUE; @@ -608,31 +501,16 @@ create_default_shell (void) } } - startup_id = pick_startup_id (); - shell = g_object_new ( E_TYPE_SHELL, "name", "org.gnome.evolution", "online", online, - "startup-id", startup_id, NULL); - g_free (startup_id); - g_signal_connect ( shell, "window-destroyed", G_CALLBACK (shell_window_destroyed_cb), NULL); - if (master_client != NULL) { - g_signal_connect ( - master_client, "save_yourself", - G_CALLBACK (master_client_save_yourself_cb), shell); - - g_signal_connect ( - master_client, "die", - G_CALLBACK (master_client_die_cb), shell); - } - g_object_unref (conf_client); /* EShell keeps its own reference to the first instance for use -- cgit