summaryrefslogtreecommitdiff
path: root/src/core/session.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/session.c')
-rw-r--r--src/core/session.c1831
1 files changed, 1831 insertions, 0 deletions
diff --git a/src/core/session.c b/src/core/session.c
new file mode 100644
index 00000000..80d22365
--- /dev/null
+++ b/src/core/session.c
@@ -0,0 +1,1831 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+/* Marco Session Management */
+
+/*
+ * Copyright (C) 2001 Havoc Pennington (some code in here from
+ * libmateui, (C) Tom Tromey, Carsten Schaar)
+ * Copyright (C) 2004, 2005 Elijah Newren
+ *
+ * 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 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.
+ */
+
+#include <config.h>
+
+#include "session.h"
+#include <X11/Xatom.h>
+
+#include <time.h>
+#include <sys/wait.h>
+
+#ifndef HAVE_SM
+void
+meta_session_init (const char *client_id,
+ const char *save_file)
+{
+ meta_topic (META_DEBUG_SM, "Compiled without session management support\n");
+}
+
+void
+meta_session_shutdown (void)
+{
+ /* nothing */
+}
+
+const MetaWindowSessionInfo*
+meta_window_lookup_saved_state (MetaWindow *window)
+{
+ return NULL;
+}
+
+void
+meta_window_release_saved_state (const MetaWindowSessionInfo *info)
+{
+ ;
+}
+#else /* HAVE_SM */
+
+#include <X11/ICE/ICElib.h>
+#include <X11/SM/SMlib.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "main.h"
+#include "util.h"
+#include "display-private.h"
+#include "workspace.h"
+
+static void ice_io_error_handler (IceConn connection);
+
+static void new_ice_connection (IceConn connection, IcePointer client_data,
+ Bool opening, IcePointer *watch_data);
+
+static void save_state (void);
+static char* load_state (const char *previous_save_file);
+static void regenerate_save_file (void);
+static const char* full_save_file (void);
+static void warn_about_lame_clients_and_finish_interact (gboolean shutdown);
+static void disconnect (void);
+
+/* This is called when data is available on an ICE connection. */
+static gboolean
+process_ice_messages (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer client_data)
+{
+ IceConn connection = (IceConn) client_data;
+ IceProcessMessagesStatus status;
+
+ /* This blocks infinitely sometimes. I don't know what
+ * to do about it. Checking "condition" just breaks
+ * session management.
+ */
+ status = IceProcessMessages (connection, NULL, NULL);
+
+ if (status == IceProcessMessagesIOError)
+ {
+#if 0
+ IcePointer context = IceGetConnectionContext (connection);
+#endif
+
+ /* We were disconnected; close our connection to the
+ * session manager, this will result in the ICE connection
+ * being cleaned up, since it is owned by libSM.
+ */
+ disconnect ();
+ meta_quit (META_EXIT_SUCCESS);
+
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* This is called when a new ICE connection is made. It arranges for
+ the ICE connection to be handled via the event loop. */
+static void
+new_ice_connection (IceConn connection, IcePointer client_data, Bool opening,
+ IcePointer *watch_data)
+{
+ guint input_id;
+
+ if (opening)
+ {
+ /* Make sure we don't pass on these file descriptors to any
+ * exec'ed children
+ */
+ GIOChannel *channel;
+
+ fcntl (IceConnectionNumber (connection), F_SETFD,
+ fcntl (IceConnectionNumber (connection), F_GETFD, 0) | FD_CLOEXEC);
+
+ channel = g_io_channel_unix_new (IceConnectionNumber (connection));
+
+ input_id = g_io_add_watch (channel,
+ G_IO_IN | G_IO_ERR,
+ process_ice_messages,
+ connection);
+
+ g_io_channel_unref (channel);
+
+ *watch_data = (IcePointer) GUINT_TO_POINTER (input_id);
+ }
+ else
+ {
+ input_id = GPOINTER_TO_UINT ((gpointer) *watch_data);
+
+ g_source_remove (input_id);
+ }
+}
+
+static IceIOErrorHandler ice_installed_handler;
+
+/* We call any handler installed before (or after) mate_ice_init but
+ avoid calling the default libICE handler which does an exit() */
+static void
+ice_io_error_handler (IceConn connection)
+{
+ if (ice_installed_handler)
+ (*ice_installed_handler) (connection);
+}
+
+static void
+ice_init (void)
+{
+ static gboolean ice_initted = FALSE;
+
+ if (! ice_initted)
+ {
+ IceIOErrorHandler default_handler;
+
+ ice_installed_handler = IceSetIOErrorHandler (NULL);
+ default_handler = IceSetIOErrorHandler (ice_io_error_handler);
+
+ if (ice_installed_handler == default_handler)
+ ice_installed_handler = NULL;
+
+ IceAddConnectionWatch (new_ice_connection, NULL);
+
+ ice_initted = TRUE;
+ }
+}
+
+typedef enum
+{
+ STATE_DISCONNECTED,
+ STATE_IDLE,
+ STATE_SAVING_PHASE_1,
+ STATE_WAITING_FOR_PHASE_2,
+ STATE_SAVING_PHASE_2,
+ STATE_WAITING_FOR_INTERACT,
+ STATE_DONE_WITH_INTERACT,
+ STATE_SKIPPING_GLOBAL_SAVE,
+ STATE_FROZEN,
+ STATE_REGISTERING
+} ClientState;
+
+static void save_phase_2_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void interact_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void shutdown_cancelled_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void save_complete_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void die_callback (SmcConn smc_conn,
+ SmPointer client_data);
+static void save_yourself_callback (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast);
+static void set_clone_restart_commands (void);
+
+static char *client_id = NULL;
+static gpointer session_connection = NULL;
+static ClientState current_state = STATE_DISCONNECTED;
+static gboolean interaction_allowed = FALSE;
+
+void
+meta_session_init (const char *previous_client_id,
+ const char *previous_save_file)
+{
+ /* Some code here from twm */
+ char buf[256];
+ unsigned long mask;
+ SmcCallbacks callbacks;
+ char *saved_client_id;
+
+ meta_topic (META_DEBUG_SM, "Initializing session with save file '%s'\n",
+ previous_save_file ? previous_save_file : "(none)");
+
+ if (previous_save_file)
+ {
+ saved_client_id = load_state (previous_save_file);
+ previous_client_id = saved_client_id;
+ }
+ else if (previous_client_id)
+ {
+ char *save_file = g_strconcat (previous_client_id, ".ms", NULL);
+ saved_client_id = load_state (save_file);
+ g_free (save_file);
+ }
+ else
+ {
+ saved_client_id = NULL;
+ }
+
+ ice_init ();
+
+ mask = SmcSaveYourselfProcMask | SmcDieProcMask |
+ SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask;
+
+ callbacks.save_yourself.callback = save_yourself_callback;
+ callbacks.save_yourself.client_data = NULL;
+
+ callbacks.die.callback = die_callback;
+ callbacks.die.client_data = NULL;
+
+ callbacks.save_complete.callback = save_complete_callback;
+ callbacks.save_complete.client_data = NULL;
+
+ callbacks.shutdown_cancelled.callback = shutdown_cancelled_callback;
+ callbacks.shutdown_cancelled.client_data = NULL;
+
+ session_connection =
+ SmcOpenConnection (NULL, /* use SESSION_MANAGER env */
+ NULL, /* means use existing ICE connection */
+ SmProtoMajor,
+ SmProtoMinor,
+ mask,
+ &callbacks,
+ (char*) previous_client_id,
+ &client_id,
+ 255, buf);
+
+ if (session_connection == NULL)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Failed to a open connection to a session manager, so window positions will not be saved: %s\n",
+ buf);
+
+ goto out;
+ }
+ else
+ {
+ if (client_id == NULL)
+ meta_bug ("Session manager gave us a NULL client ID?");
+ meta_topic (META_DEBUG_SM, "Obtained session ID '%s'\n", client_id);
+ }
+
+ if (previous_client_id && strcmp (previous_client_id, client_id) == 0)
+ current_state = STATE_IDLE;
+ else
+ current_state = STATE_REGISTERING;
+
+ {
+ SmProp prop1, prop2, prop3, prop4, prop5, prop6, *props[6];
+ SmPropValue prop1val, prop2val, prop3val, prop4val, prop5val, prop6val;
+ char pid[32];
+ char hint = SmRestartImmediately;
+ char priority = 20; /* low to run before other apps */
+
+ prop1.name = SmProgram;
+ prop1.type = SmARRAY8;
+ prop1.num_vals = 1;
+ prop1.vals = &prop1val;
+ prop1val.value = "marco";
+ prop1val.length = strlen ("marco");
+
+ /* twm sets getuid() for this, but the SM spec plainly
+ * says pw_name, twm is on crack
+ */
+ prop2.name = SmUserID;
+ prop2.type = SmARRAY8;
+ prop2.num_vals = 1;
+ prop2.vals = &prop2val;
+ prop2val.value = (char*) g_get_user_name ();
+ prop2val.length = strlen (prop2val.value);
+
+ prop3.name = SmRestartStyleHint;
+ prop3.type = SmCARD8;
+ prop3.num_vals = 1;
+ prop3.vals = &prop3val;
+ prop3val.value = &hint;
+ prop3val.length = 1;
+
+ sprintf (pid, "%d", getpid ());
+ prop4.name = SmProcessID;
+ prop4.type = SmARRAY8;
+ prop4.num_vals = 1;
+ prop4.vals = &prop4val;
+ prop4val.value = pid;
+ prop4val.length = strlen (prop4val.value);
+
+ /* Always start in home directory */
+ prop5.name = SmCurrentDirectory;
+ prop5.type = SmARRAY8;
+ prop5.num_vals = 1;
+ prop5.vals = &prop5val;
+ prop5val.value = (char*) g_get_home_dir ();
+ prop5val.length = strlen (prop5val.value);
+
+ prop6.name = "_GSM_Priority";
+ prop6.type = SmCARD8;
+ prop6.num_vals = 1;
+ prop6.vals = &prop6val;
+ prop6val.value = &priority;
+ prop6val.length = 1;
+
+ props[0] = &prop1;
+ props[1] = &prop2;
+ props[2] = &prop3;
+ props[3] = &prop4;
+ props[4] = &prop5;
+ props[5] = &prop6;
+
+ SmcSetProperties (session_connection, 6, props);
+ }
+
+ out:
+ g_free (saved_client_id);
+}
+
+void
+meta_session_shutdown (void)
+{
+ /* Change our restart mode to IfRunning */
+
+ SmProp prop1;
+ SmPropValue prop1val;
+ SmProp *props[1];
+ char hint = SmRestartIfRunning;
+
+ if (!meta_get_display ())
+ {
+ meta_verbose ("Cannot close session because there is no display");
+ return;
+ }
+
+ warn_about_lame_clients_and_finish_interact (FALSE);
+
+ if (session_connection == NULL)
+ return;
+
+ prop1.name = SmRestartStyleHint;
+ prop1.type = SmCARD8;
+ prop1.num_vals = 1;
+ prop1.vals = &prop1val;
+ prop1val.value = &hint;
+ prop1val.length = 1;
+
+ props[0] = &prop1;
+
+ SmcSetProperties (session_connection, 1, props);
+}
+
+static void
+disconnect (void)
+{
+ SmcCloseConnection (session_connection, 0, NULL);
+ session_connection = NULL;
+ current_state = STATE_DISCONNECTED;
+}
+
+static void
+save_yourself_possibly_done (gboolean shutdown,
+ gboolean successful)
+{
+ meta_topic (META_DEBUG_SM,
+ "save possibly done shutdown = %d success = %d\n",
+ shutdown, successful);
+
+ if (current_state == STATE_SAVING_PHASE_1)
+ {
+ Status status;
+
+ status = SmcRequestSaveYourselfPhase2 (session_connection,
+ save_phase_2_callback,
+ GINT_TO_POINTER (shutdown));
+
+ if (status)
+ current_state = STATE_WAITING_FOR_PHASE_2;
+
+ meta_topic (META_DEBUG_SM,
+ "Requested phase 2, status = %d\n", status);
+ }
+
+ if (current_state == STATE_SAVING_PHASE_2 &&
+ interaction_allowed)
+ {
+ Status status;
+
+ status = SmcInteractRequest (session_connection,
+ /* ignore this feature of the protocol by always
+ * claiming normal
+ */
+ SmDialogNormal,
+ interact_callback,
+ GINT_TO_POINTER (shutdown));
+
+ if (status)
+ current_state = STATE_WAITING_FOR_INTERACT;
+
+ meta_topic (META_DEBUG_SM,
+ "Requested interact, status = %d\n", status);
+ }
+
+ if (current_state == STATE_SAVING_PHASE_1 ||
+ current_state == STATE_SAVING_PHASE_2 ||
+ current_state == STATE_DONE_WITH_INTERACT ||
+ current_state == STATE_SKIPPING_GLOBAL_SAVE)
+ {
+ meta_topic (META_DEBUG_SM, "Sending SaveYourselfDone\n");
+
+ SmcSaveYourselfDone (session_connection,
+ successful);
+
+ if (shutdown)
+ current_state = STATE_FROZEN;
+ else
+ current_state = STATE_IDLE;
+ }
+}
+
+static void
+save_phase_2_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ gboolean shutdown;
+
+ meta_topic (META_DEBUG_SM, "Phase 2 save");
+
+ shutdown = GPOINTER_TO_INT (client_data);
+
+ current_state = STATE_SAVING_PHASE_2;
+
+ save_state ();
+
+ save_yourself_possibly_done (shutdown, TRUE);
+}
+
+static void
+save_yourself_callback (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast)
+{
+ gboolean successful;
+
+ meta_topic (META_DEBUG_SM, "SaveYourself received");
+
+ successful = TRUE;
+
+ /* The first SaveYourself after registering for the first time
+ * is a special case (SM specs 7.2).
+ */
+
+#if 0 /* I think the MateClient rationale for this doesn't apply */
+ if (current_state == STATE_REGISTERING)
+ {
+ current_state = STATE_IDLE;
+ /* Double check that this is a section 7.2 SaveYourself: */
+
+ if (save_style == SmSaveLocal &&
+ interact_style == SmInteractStyleNone &&
+ !shutdown && !fast)
+ {
+ /* The protocol requires this even if xsm ignores it. */
+ SmcSaveYourselfDone (session_connection, successful);
+ return;
+ }
+ }
+#endif
+
+ /* ignore Global style saves
+ *
+ * This interpretaion of the Local/Global/Both styles
+ * was discussed extensively on the xdg-list. See:
+ *
+ * https://listman.redhat.com/pipermail/xdg-list/2002-July/000615.html
+ */
+ if (save_style == SmSaveGlobal)
+ {
+ current_state = STATE_SKIPPING_GLOBAL_SAVE;
+ save_yourself_possibly_done (shutdown, successful);
+ return;
+ }
+
+ interaction_allowed = interact_style != SmInteractStyleNone;
+
+ current_state = STATE_SAVING_PHASE_1;
+
+ regenerate_save_file ();
+
+ set_clone_restart_commands ();
+
+ save_yourself_possibly_done (shutdown, successful);
+}
+
+
+static void
+die_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ meta_topic (META_DEBUG_SM, "Exiting at request of session manager\n");
+ disconnect ();
+ meta_quit (META_EXIT_SUCCESS);
+}
+
+static void
+save_complete_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ /* nothing */
+ meta_topic (META_DEBUG_SM, "SaveComplete received\n");
+}
+
+static void
+shutdown_cancelled_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ meta_topic (META_DEBUG_SM, "Shutdown cancelled received\n");
+
+ if (session_connection != NULL &&
+ (current_state != STATE_IDLE && current_state != STATE_FROZEN))
+ {
+ SmcSaveYourselfDone (session_connection, True);
+ current_state = STATE_IDLE;
+ }
+}
+
+static void
+interact_callback (SmcConn smc_conn, SmPointer client_data)
+{
+ /* nothing */
+ gboolean shutdown;
+
+ meta_topic (META_DEBUG_SM, "Interaction permission received\n");
+
+ shutdown = GPOINTER_TO_INT (client_data);
+
+ current_state = STATE_DONE_WITH_INTERACT;
+
+ warn_about_lame_clients_and_finish_interact (shutdown);
+}
+
+static void
+set_clone_restart_commands (void)
+{
+ char *restartv[10];
+ char *clonev[10];
+ char *discardv[10];
+ int i;
+ SmProp prop1, prop2, prop3, *props[3];
+
+ /* Restart (use same client ID) */
+
+ prop1.name = SmRestartCommand;
+ prop1.type = SmLISTofARRAY8;
+
+ g_return_if_fail (client_id);
+
+ i = 0;
+ restartv[i] = "marco";
+ ++i;
+ restartv[i] = "--sm-client-id";
+ ++i;
+ restartv[i] = client_id;
+ ++i;
+ restartv[i] = NULL;
+
+ prop1.vals = g_new (SmPropValue, i);
+ i = 0;
+ while (restartv[i])
+ {
+ prop1.vals[i].value = restartv[i];
+ prop1.vals[i].length = strlen (restartv[i]);
+ ++i;
+ }
+ prop1.num_vals = i;
+
+ /* Clone (no client ID) */
+
+ i = 0;
+ clonev[i] = "marco";
+ ++i;
+ clonev[i] = NULL;
+
+ prop2.name = SmCloneCommand;
+ prop2.type = SmLISTofARRAY8;
+
+ prop2.vals = g_new (SmPropValue, i);
+ i = 0;
+ while (clonev[i])
+ {
+ prop2.vals[i].value = clonev[i];
+ prop2.vals[i].length = strlen (clonev[i]);
+ ++i;
+ }
+ prop2.num_vals = i;
+
+ /* Discard */
+
+ i = 0;
+ discardv[i] = "rm";
+ ++i;
+ discardv[i] = "-f";
+ ++i;
+ discardv[i] = (char*) full_save_file ();
+ ++i;
+ discardv[i] = NULL;
+
+ prop3.name = SmDiscardCommand;
+ prop3.type = SmLISTofARRAY8;
+
+ prop3.vals = g_new (SmPropValue, i);
+ i = 0;
+ while (discardv[i])
+ {
+ prop3.vals[i].value = discardv[i];
+ prop3.vals[i].length = strlen (discardv[i]);
+ ++i;
+ }
+ prop3.num_vals = i;
+
+
+ props[0] = &prop1;
+ props[1] = &prop2;
+ props[2] = &prop3;
+
+ SmcSetProperties (session_connection, 3, props);
+
+ g_free (prop1.vals);
+ g_free (prop2.vals);
+ g_free (prop3.vals);
+}
+
+/* The remaining code in this file actually loads/saves the session,
+ * while the code above this comment handles chatting with the
+ * session manager.
+ */
+
+static const char*
+window_type_to_string (MetaWindowType type)
+{
+ switch (type)
+ {
+ case META_WINDOW_NORMAL:
+ return "normal";
+ case META_WINDOW_DESKTOP:
+ return "desktop";
+ case META_WINDOW_DOCK:
+ return "dock";
+ case META_WINDOW_DIALOG:
+ return "dialog";
+ case META_WINDOW_MODAL_DIALOG:
+ return "modal_dialog";
+ case META_WINDOW_TOOLBAR:
+ return "toolbar";
+ case META_WINDOW_MENU:
+ return "menu";
+ case META_WINDOW_SPLASHSCREEN:
+ return "splashscreen";
+ case META_WINDOW_UTILITY:
+ return "utility";
+ }
+
+ return "";
+}
+
+static MetaWindowType
+window_type_from_string (const char *str)
+{
+ if (strcmp (str, "normal") == 0)
+ return META_WINDOW_NORMAL;
+ else if (strcmp (str, "desktop") == 0)
+ return META_WINDOW_DESKTOP;
+ else if (strcmp (str, "dock") == 0)
+ return META_WINDOW_DOCK;
+ else if (strcmp (str, "dialog") == 0)
+ return META_WINDOW_DIALOG;
+ else if (strcmp (str, "modal_dialog") == 0)
+ return META_WINDOW_MODAL_DIALOG;
+ else if (strcmp (str, "toolbar") == 0)
+ return META_WINDOW_TOOLBAR;
+ else if (strcmp (str, "menu") == 0)
+ return META_WINDOW_MENU;
+ else if (strcmp (str, "utility") == 0)
+ return META_WINDOW_UTILITY;
+ else if (strcmp (str, "splashscreen") == 0)
+ return META_WINDOW_SPLASHSCREEN;
+ else
+ return META_WINDOW_NORMAL;
+}
+
+static int
+window_gravity_from_string (const char *str)
+{
+ if (strcmp (str, "NorthWestGravity") == 0)
+ return NorthWestGravity;
+ else if (strcmp (str, "NorthGravity") == 0)
+ return NorthGravity;
+ else if (strcmp (str, "NorthEastGravity") == 0)
+ return NorthEastGravity;
+ else if (strcmp (str, "WestGravity") == 0)
+ return WestGravity;
+ else if (strcmp (str, "CenterGravity") == 0)
+ return CenterGravity;
+ else if (strcmp (str, "EastGravity") == 0)
+ return EastGravity;
+ else if (strcmp (str, "SouthWestGravity") == 0)
+ return SouthWestGravity;
+ else if (strcmp (str, "SouthGravity") == 0)
+ return SouthGravity;
+ else if (strcmp (str, "SouthEastGravity") == 0)
+ return SouthEastGravity;
+ else if (strcmp (str, "StaticGravity") == 0)
+ return StaticGravity;
+ else
+ return NorthWestGravity;
+}
+
+static char*
+encode_text_as_utf8_markup (const char *text)
+{
+ /* text can be any encoding, and is nul-terminated.
+ * we pretend it's Latin-1 and encode as UTF-8
+ */
+ GString *str;
+ const char *p;
+ char *escaped;
+
+ str = g_string_new ("");
+
+ p = text;
+ while (*p)
+ {
+ g_string_append_unichar (str, *p);
+ ++p;
+ }
+
+ escaped = g_markup_escape_text (str->str, str->len);
+ g_string_free (str, TRUE);
+
+ return escaped;
+}
+
+static char*
+decode_text_from_utf8 (const char *text)
+{
+ /* Convert back from the encoded (but not escaped) UTF-8 */
+ GString *str;
+ const char *p;
+
+ str = g_string_new ("");
+
+ p = text;
+ while (*p)
+ {
+ /* obviously this barfs if the UTF-8 contains chars > 255 */
+ g_string_append_c (str, g_utf8_get_char (p));
+
+ p = g_utf8_next_char (p);
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static void
+save_state (void)
+{
+ char *marco_dir;
+ char *session_dir;
+ FILE *outfile;
+ GSList *windows;
+ GSList *tmp;
+ int stack_position;
+
+ g_assert (client_id);
+
+ outfile = NULL;
+
+ /*
+ * g_get_user_config_dir() is guaranteed to return an existing directory.
+ * Eventually, if SM stays with the WM, I'd like to make this
+ * something like <config>/window_placement in a standard format.
+ * Future optimisers should note also that by the time we get here
+ * we probably already have full_save_path figured out and therefore
+ * can just use the directory name from that.
+ */
+ marco_dir = g_strconcat (g_get_user_config_dir (),
+ G_DIR_SEPARATOR_S "marco",
+ NULL);
+
+ session_dir = g_strconcat (marco_dir,
+ G_DIR_SEPARATOR_S "sessions",
+ NULL);
+
+ if (mkdir (marco_dir, 0700) < 0 &&
+ errno != EEXIST)
+ {
+ meta_warning (_("Could not create directory '%s': %s\n"),
+ marco_dir, g_strerror (errno));
+ }
+
+ if (mkdir (session_dir, 0700) < 0 &&
+ errno != EEXIST)
+ {
+ meta_warning (_("Could not create directory '%s': %s\n"),
+ session_dir, g_strerror (errno));
+ }
+
+ meta_topic (META_DEBUG_SM, "Saving session to '%s'\n", full_save_file ());
+
+ outfile = fopen (full_save_file (), "w");
+
+ if (outfile == NULL)
+ {
+ meta_warning (_("Could not open session file '%s' for writing: %s\n"),
+ full_save_file (), g_strerror (errno));
+ goto out;
+ }
+
+ /* The file format is:
+ * <marco_session id="foo">
+ * <window id="bar" class="XTerm" name="xterm" title="/foo/bar" role="blah" type="normal" stacking="5">
+ * <workspace index="2"/>
+ * <workspace index="4"/>
+ * <sticky/> <minimized/> <maximized/>
+ * <geometry x="100" y="100" width="200" height="200" gravity="northwest"/>
+ * </window>
+ * </marco_session>
+ *
+ * Note that attributes on <window> are the match info we use to
+ * see if the saved state applies to a restored window, and
+ * child elements are the saved state to be applied.
+ *
+ */
+
+ fprintf (outfile, "<marco_session id=\"%s\">\n",
+ client_id);
+
+ windows = meta_display_list_windows (meta_get_display ());
+ stack_position = 0;
+
+ windows = g_slist_sort (windows, meta_display_stack_cmp);
+ tmp = windows;
+ stack_position = 0;
+
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ if (window->sm_client_id)
+ {
+ char *sm_client_id;
+ char *res_class;
+ char *res_name;
+ char *role;
+ char *title;
+
+ /* client id, class, name, role are not expected to be
+ * in UTF-8 (I think they are in XPCS which is Latin-1?
+ * in practice they are always ascii though.)
+ */
+
+ sm_client_id = encode_text_as_utf8_markup (window->sm_client_id);
+ res_class = window->res_class ?
+ encode_text_as_utf8_markup (window->res_class) : NULL;
+ res_name = window->res_name ?
+ encode_text_as_utf8_markup (window->res_name) : NULL;
+ role = window->role ?
+ encode_text_as_utf8_markup (window->role) : NULL;
+ if (window->title)
+ title = g_markup_escape_text (window->title, -1);
+ else
+ title = NULL;
+
+ meta_topic (META_DEBUG_SM, "Saving session managed window %s, client ID '%s'\n",
+ window->desc, window->sm_client_id);
+
+ fprintf (outfile,
+ " <window id=\"%s\" class=\"%s\" name=\"%s\" title=\"%s\" role=\"%s\" type=\"%s\" stacking=\"%d\">\n",
+ sm_client_id,
+ res_class ? res_class : "",
+ res_name ? res_name : "",
+ title ? title : "",
+ role ? role : "",
+ window_type_to_string (window->type),
+ stack_position);
+
+ g_free (sm_client_id);
+ g_free (res_class);
+ g_free (res_name);
+ g_free (role);
+ g_free (title);
+
+ /* Sticky */
+ if (window->on_all_workspaces)
+ fputs (" <sticky/>\n", outfile);
+
+ /* Minimized */
+ if (window->minimized)
+ fputs (" <minimized/>\n", outfile);
+
+ /* Maximized */
+ if (META_WINDOW_MAXIMIZED (window))
+ {
+ fprintf (outfile,
+ " <maximized saved_x=\"%d\" saved_y=\"%d\" saved_width=\"%d\" saved_height=\"%d\"/>\n",
+ window->saved_rect.x,
+ window->saved_rect.y,
+ window->saved_rect.width,
+ window->saved_rect.height);
+ }
+
+ /* Workspaces we're on */
+ {
+ int n;
+ n = meta_workspace_index (window->workspace);
+ fprintf (outfile,
+ " <workspace index=\"%d\"/>\n", n);
+ }
+
+ /* Gravity */
+ {
+ int x, y, w, h;
+ meta_window_get_geometry (window, &x, &y, &w, &h);
+
+ fprintf (outfile,
+ " <geometry x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" gravity=\"%s\"/>\n",
+ x, y, w, h,
+ meta_gravity_to_string (window->size_hints.win_gravity));
+ }
+
+ fputs (" </window>\n", outfile);
+ }
+ else
+ {
+ meta_topic (META_DEBUG_SM, "Not saving window '%s', not session managed\n",
+ window->desc);
+ }
+
+ tmp = tmp->next;
+ ++stack_position;
+ }
+
+ g_slist_free (windows);
+
+ fputs ("</marco_session>\n", outfile);
+
+ out:
+ if (outfile)
+ {
+ /* FIXME need a dialog for this */
+ if (ferror (outfile))
+ {
+ meta_warning (_("Error writing session file '%s': %s\n"),
+ full_save_file (), g_strerror (errno));
+ }
+ if (fclose (outfile))
+ {
+ meta_warning (_("Error closing session file '%s': %s\n"),
+ full_save_file (), g_strerror (errno));
+ }
+ }
+
+ g_free (marco_dir);
+ g_free (session_dir);
+}
+
+typedef enum
+{
+ WINDOW_TAG_NONE,
+ WINDOW_TAG_DESKTOP,
+ WINDOW_TAG_STICKY,
+ WINDOW_TAG_MINIMIZED,
+ WINDOW_TAG_MAXIMIZED,
+ WINDOW_TAG_GEOMETRY
+} WindowTag;
+
+typedef struct
+{
+ MetaWindowSessionInfo *info;
+ char *previous_id;
+} ParseData;
+
+static void session_info_free (MetaWindowSessionInfo *info);
+static MetaWindowSessionInfo* session_info_new (void);
+
+static void start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error);
+static void end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error);
+static void text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error);
+
+static GMarkupParser marco_session_parser = {
+ start_element_handler,
+ end_element_handler,
+ text_handler,
+ NULL,
+ NULL
+};
+
+static GSList *window_info_list = NULL;
+
+static char*
+load_state (const char *previous_save_file)
+{
+ GMarkupParseContext *context;
+ GError *error;
+ ParseData parse_data;
+ char *text;
+ gsize length;
+ char *session_file;
+
+ session_file = g_strconcat (g_get_user_config_dir (),
+ G_DIR_SEPARATOR_S "marco"
+ G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
+ previous_save_file,
+ NULL);
+
+ error = NULL;
+ if (!g_file_get_contents (session_file,
+ &text,
+ &length,
+ &error))
+ {
+ char *canonical_session_file = session_file;
+
+ /* Maybe they were doing it the old way, with ~/.marco */
+ session_file = g_strconcat (g_get_home_dir (),
+ G_DIR_SEPARATOR_S ".marco"
+ G_DIR_SEPARATOR_S "sessions"
+ G_DIR_SEPARATOR_S,
+ previous_save_file,
+ NULL);
+
+ if (!g_file_get_contents (session_file,
+ &text,
+ &length,
+ NULL))
+ {
+ /* oh, just give up */
+
+ g_error_free (error);
+ g_free (session_file);
+ g_free (canonical_session_file);
+ return NULL;
+ }
+
+ g_free (canonical_session_file);
+ }
+
+ meta_topic (META_DEBUG_SM, "Parsing saved session file %s\n", session_file);
+ g_free (session_file);
+ session_file = NULL;
+
+ parse_data.info = NULL;
+ parse_data.previous_id = NULL;
+
+ context = g_markup_parse_context_new (&marco_session_parser,
+ 0, &parse_data, NULL);
+
+ error = NULL;
+ if (!g_markup_parse_context_parse (context,
+ text,
+ length,
+ &error))
+ goto error;
+
+
+ error = NULL;
+ if (!g_markup_parse_context_end_parse (context, &error))
+ goto error;
+
+ g_markup_parse_context_free (context);
+
+ goto out;
+
+ error:
+
+ meta_warning (_("Failed to parse saved session file: %s\n"),
+ error->message);
+ g_error_free (error);
+
+ if (parse_data.info)
+ session_info_free (parse_data.info);
+
+ g_free (parse_data.previous_id);
+ parse_data.previous_id = NULL;
+
+ out:
+
+ g_free (text);
+
+ return parse_data.previous_id;
+}
+
+/* FIXME this isn't very robust against bogus session files */
+static void
+start_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *pd;
+
+ pd = user_data;
+
+ if (strcmp (element_name, "marco_session") == 0)
+ {
+ /* Get previous ID */
+ int i;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (pd->previous_id)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("<marco_session> attribute seen but we already have the session ID"));
+ return;
+ }
+
+ if (strcmp (name, "id") == 0)
+ {
+ pd->previous_id = decode_text_from_utf8 (val);
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "marco_session");
+ return;
+ }
+
+ ++i;
+ }
+ }
+ else if (strcmp (element_name, "window") == 0)
+ {
+ int i;
+
+ if (pd->info)
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_PARSE,
+ _("nested <window> tag"));
+ return;
+ }
+
+ pd->info = session_info_new ();
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (strcmp (name, "id") == 0)
+ {
+ if (*val)
+ pd->info->id = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "class") == 0)
+ {
+ if (*val)
+ pd->info->res_class = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "name") == 0)
+ {
+ if (*val)
+ pd->info->res_name = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "title") == 0)
+ {
+ if (*val)
+ pd->info->title = g_strdup (val);
+ }
+ else if (strcmp (name, "role") == 0)
+ {
+ if (*val)
+ pd->info->role = decode_text_from_utf8 (val);
+ }
+ else if (strcmp (name, "type") == 0)
+ {
+ if (*val)
+ pd->info->type = window_type_from_string (val);
+ }
+ else if (strcmp (name, "stacking") == 0)
+ {
+ if (*val)
+ {
+ pd->info->stack_position = atoi (val);
+ pd->info->stack_position_set = TRUE;
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "window");
+ session_info_free (pd->info);
+ pd->info = NULL;
+ return;
+ }
+
+ ++i;
+ }
+ }
+ else if (strcmp (element_name, "workspace") == 0)
+ {
+ int i;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+
+ name = attribute_names[i];
+
+ if (strcmp (name, "index") == 0)
+ {
+ pd->info->workspace_indices =
+ g_slist_prepend (pd->info->workspace_indices,
+ GINT_TO_POINTER (atoi (attribute_values[i])));
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "window");
+ session_info_free (pd->info);
+ pd->info = NULL;
+ return;
+ }
+
+ ++i;
+ }
+ }
+ else if (strcmp (element_name, "sticky") == 0)
+ {
+ pd->info->on_all_workspaces = TRUE;
+ pd->info->on_all_workspaces_set = TRUE;
+ }
+ else if (strcmp (element_name, "minimized") == 0)
+ {
+ pd->info->minimized = TRUE;
+ pd->info->minimized_set = TRUE;
+ }
+ else if (strcmp (element_name, "maximized") == 0)
+ {
+ int i;
+
+ i = 0;
+ pd->info->maximized = TRUE;
+ pd->info->maximized_set = TRUE;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (strcmp (name, "saved_x") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.x = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else if (strcmp (name, "saved_y") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.y = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else if (strcmp (name, "saved_width") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.width = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else if (strcmp (name, "saved_height") == 0)
+ {
+ if (*val)
+ {
+ pd->info->saved_rect.height = atoi (val);
+ pd->info->saved_rect_set = TRUE;
+ }
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "maximized");
+ return;
+ }
+
+ ++i;
+ }
+
+ if (pd->info->saved_rect_set)
+ meta_topic (META_DEBUG_SM, "Saved unmaximized size %d,%d %dx%d \n",
+ pd->info->saved_rect.x,
+ pd->info->saved_rect.y,
+ pd->info->saved_rect.width,
+ pd->info->saved_rect.height);
+ }
+ else if (strcmp (element_name, "geometry") == 0)
+ {
+ int i;
+
+ pd->info->geometry_set = TRUE;
+
+ i = 0;
+ while (attribute_names[i])
+ {
+ const char *name;
+ const char *val;
+
+ name = attribute_names[i];
+ val = attribute_values[i];
+
+ if (strcmp (name, "x") == 0)
+ {
+ if (*val)
+ pd->info->rect.x = atoi (val);
+ }
+ else if (strcmp (name, "y") == 0)
+ {
+ if (*val)
+ pd->info->rect.y = atoi (val);
+ }
+ else if (strcmp (name, "width") == 0)
+ {
+ if (*val)
+ pd->info->rect.width = atoi (val);
+ }
+ else if (strcmp (name, "height") == 0)
+ {
+ if (*val)
+ pd->info->rect.height = atoi (val);
+ }
+ else if (strcmp (name, "gravity") == 0)
+ {
+ if (*val)
+ pd->info->gravity = window_gravity_from_string (val);
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
+ _("Unknown attribute %s on <%s> element"),
+ name, "geometry");
+ return;
+ }
+
+ ++i;
+ }
+
+ meta_topic (META_DEBUG_SM, "Loaded geometry %d,%d %dx%d gravity %s\n",
+ pd->info->rect.x,
+ pd->info->rect.y,
+ pd->info->rect.width,
+ pd->info->rect.height,
+ meta_gravity_to_string (pd->info->gravity));
+ }
+ else
+ {
+ g_set_error (error,
+ G_MARKUP_ERROR,
+ G_MARKUP_ERROR_UNKNOWN_ELEMENT,
+ _("Unknown element %s"),
+ element_name);
+ return;
+ }
+}
+
+static void
+end_element_handler (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ ParseData *pd;
+
+ pd = user_data;
+
+ if (strcmp (element_name, "window") == 0)
+ {
+ g_assert (pd->info);
+
+ window_info_list = g_slist_prepend (window_info_list,
+ pd->info);
+
+ meta_topic (META_DEBUG_SM, "Loaded window info from session with class: %s name: %s role: %s\n",
+ pd->info->res_class ? pd->info->res_class : "(none)",
+ pd->info->res_name ? pd->info->res_name : "(none)",
+ pd->info->role ? pd->info->role : "(none)");
+
+ pd->info = NULL;
+ }
+}
+
+static void
+text_handler (GMarkupParseContext *context,
+ const gchar *text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ /* Right now we don't have any elements where we care about their
+ * content
+ */
+}
+
+static gboolean
+both_null_or_matching (const char *a,
+ const char *b)
+{
+ if (a == NULL && b == NULL)
+ return TRUE;
+ else if (a && b && strcmp (a, b) == 0)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static GSList*
+get_possible_matches (MetaWindow *window)
+{
+ /* Get all windows with this client ID */
+ GSList *retval;
+ GSList *tmp;
+ gboolean ignore_client_id;
+
+ retval = NULL;
+
+ ignore_client_id = g_getenv ("MARCO_DEBUG_SM") != NULL;
+
+ tmp = window_info_list;
+ while (tmp != NULL)
+ {
+ MetaWindowSessionInfo *info;
+
+ info = tmp->data;
+
+ if ((ignore_client_id ||
+ both_null_or_matching (info->id, window->sm_client_id)) &&
+ both_null_or_matching (info->res_class, window->res_class) &&
+ both_null_or_matching (info->res_name, window->res_name) &&
+ both_null_or_matching (info->role, window->role))
+ {
+ meta_topic (META_DEBUG_SM, "Window %s may match saved window with class: %s name: %s role: %s\n",
+ window->desc,
+ info->res_class ? info->res_class : "(none)",
+ info->res_name ? info->res_name : "(none)",
+ info->role ? info->role : "(none)");
+
+ retval = g_slist_prepend (retval, info);
+ }
+ else
+ {
+ if (meta_is_verbose ())
+ {
+ if (!both_null_or_matching (info->id, window->sm_client_id))
+ meta_topic (META_DEBUG_SM, "Window %s has SM client ID %s, saved state has %s, no match\n",
+ window->desc,
+ window->sm_client_id ? window->sm_client_id : "(none)",
+ info->id ? info->id : "(none)");
+ else if (!both_null_or_matching (info->res_class, window->res_class))
+ meta_topic (META_DEBUG_SM, "Window %s has class %s doesn't match saved class %s, no match\n",
+ window->desc,
+ window->res_class ? window->res_class : "(none)",
+ info->res_class ? info->res_class : "(none)");
+
+ else if (!both_null_or_matching (info->res_name, window->res_name))
+ meta_topic (META_DEBUG_SM, "Window %s has name %s doesn't match saved name %s, no match\n",
+ window->desc,
+ window->res_name ? window->res_name : "(none)",
+ info->res_name ? info->res_name : "(none)");
+ else if (!both_null_or_matching (info->role, window->role))
+ meta_topic (META_DEBUG_SM, "Window %s has role %s doesn't match saved role %s, no match\n",
+ window->desc,
+ window->role ? window->role : "(none)",
+ info->role ? info->role : "(none)");
+ else
+ meta_topic (META_DEBUG_SM, "???? should not happen - window %s doesn't match saved state %s for no good reason\n",
+ window->desc, info->id);
+ }
+ }
+
+ tmp = tmp->next;
+ }
+
+ return retval;
+}
+
+static const MetaWindowSessionInfo*
+find_best_match (GSList *infos,
+ MetaWindow *window)
+{
+ GSList *tmp;
+ const MetaWindowSessionInfo *matching_title;
+ const MetaWindowSessionInfo *matching_type;
+
+ matching_title = NULL;
+ matching_type = NULL;
+
+ tmp = infos;
+ while (tmp != NULL)
+ {
+ MetaWindowSessionInfo *info;
+
+ info = tmp->data;
+
+ if (matching_title == NULL &&
+ both_null_or_matching (info->title, window->title))
+ matching_title = info;
+
+ if (matching_type == NULL &&
+ info->type == window->type)
+ matching_type = info;
+
+ tmp = tmp->next;
+ }
+
+ /* Prefer same title, then same type of window, then
+ * just pick something. Eventually we could enhance this
+ * to e.g. break ties by geometry hint similarity,
+ * or other window features.
+ */
+
+ if (matching_title)
+ return matching_title;
+ else if (matching_type)
+ return matching_type;
+ else
+ return infos->data;
+}
+
+const MetaWindowSessionInfo*
+meta_window_lookup_saved_state (MetaWindow *window)
+{
+ GSList *possibles;
+ const MetaWindowSessionInfo *info;
+
+ /* Window is not session managed.
+ * I haven't yet figured out how to deal with these
+ * in a way that doesn't cause broken side effects in
+ * situations other than on session restore.
+ */
+ if (window->sm_client_id == NULL)
+ {
+ meta_topic (META_DEBUG_SM,
+ "Window %s is not session managed, not checking for saved state\n",
+ window->desc);
+ return NULL;
+ }
+
+ possibles = get_possible_matches (window);
+
+ if (possibles == NULL)
+ {
+ meta_topic (META_DEBUG_SM, "Window %s has no possible matches in the list of saved window states\n",
+ window->desc);
+ return NULL;
+ }
+
+ info = find_best_match (possibles, window);
+
+ g_slist_free (possibles);
+
+ return info;
+}
+
+void
+meta_window_release_saved_state (const MetaWindowSessionInfo *info)
+{
+ /* We don't want to use the same saved state again for another
+ * window.
+ */
+ window_info_list = g_slist_remove (window_info_list, info);
+
+ session_info_free ((MetaWindowSessionInfo*) info);
+}
+
+static void
+session_info_free (MetaWindowSessionInfo *info)
+{
+ g_free (info->id);
+ g_free (info->res_class);
+ g_free (info->res_name);
+ g_free (info->title);
+ g_free (info->role);
+
+ g_slist_free (info->workspace_indices);
+
+ g_free (info);
+}
+
+static MetaWindowSessionInfo*
+session_info_new (void)
+{
+ MetaWindowSessionInfo *info;
+
+ info = g_new0 (MetaWindowSessionInfo, 1);
+
+ info->type = META_WINDOW_NORMAL;
+ info->gravity = NorthWestGravity;
+
+ return info;
+}
+
+static char* full_save_path = NULL;
+
+static void
+regenerate_save_file (void)
+{
+ g_free (full_save_path);
+
+ if (client_id)
+ full_save_path = g_strconcat (g_get_user_config_dir (),
+ G_DIR_SEPARATOR_S "marco"
+ G_DIR_SEPARATOR_S "sessions" G_DIR_SEPARATOR_S,
+ client_id,
+ ".ms",
+ NULL);
+ else
+ full_save_path = NULL;
+}
+
+static const char*
+full_save_file (void)
+{
+ return full_save_path;
+}
+
+static int
+windows_cmp_by_title (MetaWindow *a,
+ MetaWindow *b)
+{
+ return g_utf8_collate (a->title, b->title);
+}
+
+static void
+finish_interact (gboolean shutdown)
+{
+ if (current_state == STATE_DONE_WITH_INTERACT) /* paranoia */
+ {
+ SmcInteractDone (session_connection, False /* don't cancel logout */);
+
+ save_yourself_possibly_done (shutdown, TRUE);
+ }
+}
+
+static void
+dialog_closed (GPid pid, int status, gpointer user_data)
+{
+ gboolean shutdown = GPOINTER_TO_INT (user_data);
+
+ if (WIFEXITED (status) && WEXITSTATUS (status) == 0) /* pressed "OK" */
+ {
+ finish_interact (shutdown);
+ }
+}
+
+static void
+warn_about_lame_clients_and_finish_interact (gboolean shutdown)
+{
+ GSList *lame = NULL;
+ GSList *windows;
+ GSList *lame_details = NULL;
+ GSList *tmp;
+ GSList *columns = NULL;
+ GPid pid;
+
+ windows = meta_display_list_windows (meta_get_display ());
+ tmp = windows;
+ while (tmp != NULL)
+ {
+ MetaWindow *window;
+
+ window = tmp->data;
+
+ /* only complain about normal windows, the others
+ * are kind of dumb to worry about
+ */
+ if (window->sm_client_id == NULL &&
+ window->type == META_WINDOW_NORMAL)
+ lame = g_slist_prepend (lame, window);
+
+ tmp = tmp->next;
+ }
+
+ g_slist_free (windows);
+
+ if (lame == NULL)
+ {
+ /* No lame apps. */
+ finish_interact (shutdown);
+ return;
+ }
+
+ columns = g_slist_prepend (columns, "Window");
+ columns = g_slist_prepend (columns, "Class");
+
+ lame = g_slist_sort (lame, (GCompareFunc) windows_cmp_by_title);
+
+ tmp = lame;
+ while (tmp != NULL)
+ {
+ MetaWindow *w = tmp->data;
+
+ lame_details = g_slist_prepend (lame_details,
+ w->res_class ? w->res_class : "");
+ lame_details = g_slist_prepend (lame_details,
+ w->title);
+
+ tmp = tmp->next;
+ }
+ g_slist_free (lame);
+
+ pid = meta_show_dialog("--list",
+ _("These windows do not support &quot;save current setup&quot; "
+ "and will have to be restarted manually next time "
+ "you log in."),
+ "240",
+ meta_screen_get_screen_number (meta_get_display()->active_screen),
+ NULL, NULL,
+ None,
+ columns,
+ lame_details);
+
+ g_slist_free (lame_details);
+
+ g_child_watch_add (pid, dialog_closed, GINT_TO_POINTER (shutdown));
+}
+
+#endif /* HAVE_SM */