>From 2441b33f2907b8061e0b24aa409466daad934091 Mon Sep 17 00:00:00 2001
From: Eugene Arshinov <earshinov@gmail.com>
Date: Sun, 13 Dec 2009 14:52:39 +0300
Subject: [PATCH 1/7] Extract SM-related code into separate sm.{c,h} files, make some refactoring, and write code comments for Doxygen.

---
 doc/Doxyfile.in    |    2 +-
 src/Makefile.am    |    2 +-
 src/main.c         |  310 +---------------------------
 src/makefile.win32 |    2 +-
 src/sm.c           |  603 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/sm.h           |   28 +++
 wscript            |    2 +-
 7 files changed, 637 insertions(+), 312 deletions(-)
 create mode 100644 src/sm.c
 create mode 100644 src/sm.h

diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in
index 82e96ec..b753373 100644
--- a/doc/Doxyfile.in
+++ b/doc/Doxyfile.in
@@ -229,7 +229,7 @@ SEARCH_INCLUDES        = NO
 INCLUDE_PATH           =
 INCLUDE_FILE_PATTERNS  =
 # make G_GNUC_PRINTF a no-op unless doxygen would ignore functions with varargs
-PREDEFINED             = "G_GNUC_PRINTF(x,y)=" HAVE_PLUGINS GEANY_FUNCTIONS_H
+PREDEFINED             = "G_GNUC_PRINTF(x,y)=" HAVE_PLUGINS GEANY_FUNCTIONS_H HAVE_LIBSM
 EXPAND_AS_DEFINED      =
 SKIP_FUNCTION_MACROS   = NO
 #---------------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index 9f9b276..1a9615d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -38,6 +38,7 @@ SRCS = \
 	project.c project.h \
 	sciwrappers.c sciwrappers.h \
 	search.c search.h \
+	sm.c sm.h \
 	socket.c socket.h \
 	stash.c stash.h \
 	support.h \
@@ -122,4 +123,3 @@ AM_CFLAGS = -DGEANY_DATADIR=\""$(datadir)"\" \
 clean-local:
 
 endif
-
diff --git a/src/main.c b/src/main.c
index f5aed42..db7521d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -74,6 +74,7 @@
 #include "printing.h"
 #include "toolbar.h"
 #include "geanyobject.h"
+#include "sm.h"
 
 #ifdef HAVE_SOCKET
 # include "socket.h"
@@ -83,11 +84,6 @@
 # include "vte.h"
 #endif
 
-#ifdef HAVE_LIBSM
-# include <X11/SM/SMlib.h>
-# include <X11/ICE/ICElib.h>
-#endif
-
 #ifndef N_
 # define N_(String) (String)
 #endif
@@ -154,306 +150,6 @@ static GOptionEntry entries[] =
 };
 
 
-#ifdef HAVE_LIBSM
-	/*
-	 * As libSM is not available on Windows,
-	 * it is safe enough to use POSIX-specific things here.
-	 *
-	 * Note that we have to support ICE (Inter-Client Exchange Protocol)
-	 * in order to support XSMP (X Session Management Protocol). So both
-	 * are initialized here.
-	 *
-	 * Usage: sm_init() is called from the main() function.
-	 */
-
-	/* --- ICE --- */
-
-static gboolean ice_process_messages(GIOChannel * source, GIOCondition condition, gpointer data)
-{
-	IceConn icecon = (IceConn)data;
-	IceProcessMessages(icecon, NULL, NULL);
-	return TRUE;
-}
-
-static void ice_handle_connection(IceConn icecon, IcePointer client_data,
-	Bool opening, IcePointer *watch_data)
-{
-	guint input_id;
-
-	if (opening)
-	{
-		/*
-		 * Install a GLib IO Channel to process ICE messages coming from this connection
-		 * in our GTK event loop. This is how session manager communicates to us.
-		 */
-		GIOChannel * channel = g_io_channel_unix_new(IceConnectionNumber(icecon));
-		input_id = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
-			ice_process_messages, icecon);
-		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);
-	}
-}
-
-	/* --- libSM: data --- */
-
-SmPropValue sm_program_val;
-SmPropValue sm_client_id_arg_val;
-
-	/* --- libSM: utility functions --- */
-
-static void sm_set_command_props(SmcConn smcon)
-{
-	/*
-	 * According to libSM documentation, client-ID should be saved as part of the
-	 * SmRestartCommand so that the client will retain the same ID after it is
-	 * restarted. We use '--libsm-client-id' command-line option for that.
-	 */
-
-	/*
-	 * FIXME: We have to specify '--no-session' command-line argument in commands.
-	 *
-	 * Reason:
-	 *
-	 *   Currently all Geany instances try to save session. Consider the
-	 *   following use case.
-	 *
-	 *   User creates two instances of geany, a "main" one (for example, using
-	 *   the main menu of his DE) and a "non-main" one typing 'geany --new-instance'
-	 *   in a terminal emulator. When this user logouts, session manager sends
-	 *   termination messages to Geany instances in unpredictable order. Geany
-	 *   session will be saved by the instance that was last to handle the
-	 *   message. Suppose that was the "non-main" Geany instance.
-	 *
-	 *   When the user logins again, session manager restores Geany instances,
-	 *   again in unpredictable order. If we do not supply '--no-session' argument,
-	 *   the "main" instance will catch the session stored by the "non-main"
-	 *   one, which is not desired behaviour.
-	 *
-	 * Drawbacks of the '--no-session' solution:
-	 *
-	 *   The "main" instance won't save Geany session as required.
-	 *
-	 * Possible fixes:
-	 *
-	 *   * Disable saving of Geany session for "non-main" Geany instances.
-	 *     Sounds sensible and applicable.
-	 *
-	 *   * Save session even when '--no-session' option is specified (i.e.,
-	 *     consider this options only when reading Geany session). Sounds
-	 *     non-applicable as the described behaviour does not match
-	 *     even the option's name.
-	 *
-	 *   * Create a separate option to be used when reading Geany session
-	 *     is needed and writing Geany session is forbidden. Sounds awkward.
-	 */
-
-	const gint page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
-	gint page;
-
-		/*
-		 * Allocate space for `page_count+4' elements:
-		 *   * tree elements for program name, client-ID and '--no-session' option;
-		 *   * possibly one element for '--new-instance' option;
-		 *   * max `page_count' elements for file paths.
-		 * Store the number of actually used elements in `arr_real_len'.
-		 */
-	GArray * arr = g_array_sized_new(FALSE, FALSE, sizeof(SmPropValue), page_count+4);
-	gint arr_real_len = 0;
-	SmPropValue * val;
-	SmProp * prop;
-
-	((SmPropValue *)arr->data)[0] = sm_program_val;
-	((SmPropValue *)arr->data)[1] = sm_client_id_arg_val;
-	((SmPropValue *)arr->data)[2].length = 2; /* length of "-s" */
-	((SmPropValue *)arr->data)[2].value = "-s";
-	arr_real_len = 3;
-
-#ifdef HAVE_SOCKET
-	if (cl_options.new_instance)
-	{
-		val = ((SmPropValue *)arr->data) + arr_real_len++;
-		val->length = 2; /* length of "-i" */
-		val->value = "-i";
-	}
-#endif
-	/* TODO: handle other command-line options */
-
-	for (page = 0; page < page_count; page++)
-	{
-		GeanyDocument * doc = document_get_from_page(page);
-		if (doc->real_path)
-		{
-			val = ((SmPropValue *)arr->data) + arr_real_len++;
-			val->length = strlen(doc->real_path);
-			val->value = doc->real_path;
-		}
-	}
-
-	SmProp restart_command_prop = {
-		SmRestartCommand,
-		SmLISTofARRAY8,
-		arr_real_len,
-		(SmPropValue *)arr->data};
-	prop = &restart_command_prop;
-	SmcSetProperties(smcon, 1, &prop);
-
-		/*
-		 * We should not specify client-ID in SmCloneCommand,
-		 * so "remove" the corresponding element from `arr'.
-		 */
-	((SmPropValue *)arr->data)[1] = sm_program_val;
-	SmProp clone_command_prop = {
-		SmCloneCommand,
-		SmLISTofARRAY8,
-		arr_real_len-1,
-		((SmPropValue *)arr->data)+1};
-	prop = &clone_command_prop;
-	SmcSetProperties(smcon, 1, &prop);
-
-	g_array_free(arr, TRUE);
-}
-
-	/* --- libSM: callbacks --- */
-
-static void sm_interact_callback(SmcConn smcon, SmPointer client_data)
-{
-	gboolean interactive = (gboolean)client_data;
-	gboolean cancelled = !main_save(interactive);
-
-	sm_set_command_props(smcon);
-	SmcInteractDone(smcon, cancelled);
-}
-
-static void sm_die_callback(SmcConn smcon, SmPointer client_data)
-{
-	SmcCloseConnection(smcon, 0, NULL);
-	main_finalize();
-}
-
-static void sm_save_complete_callback(SmcConn smcon, SmPointer client_data)
-{
-}
-
-static void sm_shutdown_cancelled_callback(SmcConn smcon, SmPointer client_data)
-{
-	SmcSaveYourselfDone(smcon, TRUE);
-}
-
-static void sm_save_yourself_callback (SmcConn smcon, SmPointer client_data,
-	int save_style, gboolean shutdown, int interact_style, gboolean fast)
-{
-
-	if ((save_style == SmSaveGlobal || save_style == SmSaveBoth) && document_any_unsaved())
-	{
-		if (!SmcInteractRequest(smcon, SmDialogNormal, sm_interact_callback,
-				  (gpointer)(interact_style == SmInteractStyleAny)))
-			SmcSaveYourselfDone(smcon, FALSE);
-
-		return;
-	}
-
-	/*
-	 * TODO: libSM documentation says that when the system is being shutting down
-	 * (`shutdown' argument is True), we should disable user interaction here
-	 * until we get "Die" or "Shutdown" message.
-	 */
-
-	sm_set_command_props(smcon);
-	SmcSaveYourselfDone(smcon, TRUE);
-}
-
-	/* --- libSM: sm_init() --- */
-
-static void sm_init(char * argv0)
-{
-	if (!g_getenv("SESSION_MANAGER"))
-		return;
-
-	IceAddConnectionWatch(ice_handle_connection, NULL);
-
-	SmcCallbacks callbacks = {
-		{sm_save_yourself_callback, NULL},
-		{sm_die_callback, NULL},
-		{sm_save_complete_callback, NULL},
-		{sm_shutdown_cancelled_callback, NULL}};
-
-	gchar * client_id;
-	gchar err[256] = "";
-	SmcConn smcon = (gpointer)SmcOpenConnection(NULL, NULL,
-		SmProtoMajor, SmProtoMinor,
-		SmcSaveYourselfProcMask |
-		SmcDieProcMask |
-		SmcSaveCompleteProcMask |
-		SmcShutdownCancelledProcMask,
-		&callbacks,
-		libsm_client_id, &client_id,
-		256, err);
-
-	if (!smcon)
-	{
-		g_warning("While connecting to session manager:\n%s.", err);
-		return;
-	}
-
-
-	const gchar * username = g_get_user_name();
-	gchar * curdir = g_get_current_dir();
-
-	SmPropValue userid_val = {
-		(username ? strlen(username) : 0),
-		username ? (char *)username : ""};
-	SmPropValue curdir_val = {
-		strlen(curdir),
-		curdir};
-
-	SmProp program_prop = {SmProgram, SmARRAY8, 1, &sm_program_val};
-	SmProp userid_prop = {SmUserID, SmARRAY8, 1, &userid_val};
-	SmProp curdir_prop = {SmCurrentDirectory, SmARRAY8, 1, &curdir_val};
-
-	SmProp * proplist[3] = {
-		&program_prop,
-		&userid_prop,
-		&curdir_prop
-	};
-	SmcSetProperties(smcon, 3, proplist);
-
-
-	/*
-	 * Required SmCloneCommand and SmRestartCommand properties are set later
-	 * as their values may change in runtime. See also sm_set_command_props().
-	 */
-
-	gchar * client_id_arg = g_strconcat("--libsm-client-id=", client_id, NULL); /* never freed */
-
-		/*
-		 * If this instance is run with path (e.g., "./geany", "src/geany",
-		 * "/usr/local/bin/geany"), specify absolute path in restart command.
-		 * Relative paths may not work even if we set SmCurrentDirectory prop.
-		 *
-		 * Otherwise leave argv[0] as is (geany will be found
-		 * using PATH environment variable).
-		 */
-	gchar * executable_path = (strchr(argv0, G_DIR_SEPARATOR) == NULL) ? argv0 :
-		g_build_filename(curdir, argv0, NULL); /* never freed */
-
-	sm_program_val.length = strlen(executable_path);
-	sm_program_val.value = executable_path;
-	sm_client_id_arg_val.length = strlen(client_id_arg);
-	sm_client_id_arg_val.value = client_id_arg;
-
-
-	free(client_id);
-	g_free(curdir);
-}
-#endif
-
-
 static void setup_window_position(void)
 {
 	/* interprets the saved window geometry */
@@ -1255,9 +951,7 @@ gint main(gint argc, gchar **argv)
 	}
 #endif
 
-#ifdef HAVE_LIBSM
-	sm_init(argv[0]);
-#endif
+	sm_init(argv[0], libsm_client_id);
 
 	geany_debug("Geany %s, GTK+ %u.%u.%u, GLib %u.%u.%u",
 		main_get_version_string(),
diff --git a/src/makefile.win32 b/src/makefile.win32
index 344647d..f3e25a2 100644
--- a/src/makefile.win32
+++ b/src/makefile.win32
@@ -65,7 +65,7 @@ OBJS =	about.o build.o callbacks.o dialogs.o document.o editor.o encodings.o fil
 		interface.o keybindings.o keyfile.o \
 		log.o main.o msgwindow.o navqueue.o notebook.o plugins.o pluginutils.o \
 		prefs.o printing.o project.o \
-		sciwrappers.o search.o socket.o stash.o \
+		sciwrappers.o search.o sm.o socket.o stash.o \
 		symbols.o templates.o toolbar.o tools.o sidebar.o \
 		ui_utils.o utils.o win32.o
 
diff --git a/src/sm.c b/src/sm.c
new file mode 100644
index 0000000..0112274
--- /dev/null
+++ b/src/sm.c
@@ -0,0 +1,603 @@
+/*
+ *      sm.c - this file is part of Geany, a fast and lightweight IDE
+ *
+ *      Copyright 2009 Eugene Arshinov <earshinov(at)gmail(dot)com>
+ *
+ *      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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $Id$
+ */
+
+/**
+ * @file sm.c
+ * Provides X session management protocol (XSMP) support using libSM library.
+ *
+ * In order to support XSMP, we have to support Inter-Client Exchange
+ * Protocol (ICE). This file takes care of the latter too.
+ *
+ * Typical usage: @c sm_init() is called when Geany is starting.
+ *
+ * According to libSM documentation, client should retain the same ID after
+ * it is restarted. The main module (@c main.c) maintains "--libsm-client-id"
+ * command-line option and passes the specified value (if any) to @c sm_init().
+ */
+
+/*
+ * As libSM is not available on Windows,
+ * it is safe enough to use POSIX-specific things here.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef HAVE_LIBSM
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <X11/SM/SMlib.h>
+#include <X11/ICE/ICElib.h>
+
+#include "geany.h"
+#include "main.h" /* for cl_options */
+#include "ui_utils.h" /* access main_widgets.notebook to iterate over opened docs */
+#include "document.h"
+
+
+static void ice_connection_watch(IceConn icecon, IcePointer client_data, Bool opening,
+    IcePointer *watch_data);
+static gboolean ice_iochannel_watch(GIOChannel * source, GIOCondition condition, gpointer data);
+
+static SmcConn sm_connect(const char * libsm_client_id, char ** new_client_id);
+
+static void sm_set_constant_props(SmcConn smcon);
+static void sm_store_props(const char * argv0, const char * libsm_client_id);
+static void sm_set_runtime_props(SmcConn smcon);
+
+static void sm_save_yourself_callback(SmcConn smcon, SmPointer client_data,
+	int save_type, Bool shutdown, int interact_style, Bool fast);
+static void sm_interact_callback(SmcConn smcon, SmPointer client_data);
+static void sm_save_complete_callback(SmcConn smcon, SmPointer client_data);
+static void sm_shutdown_cancelled_callback(SmcConn smcon, SmPointer client_data);
+static void sm_die_callback(SmcConn smcon, SmPointer client_data);
+
+
+/**
+ * @c SmPropValue storing a path to Geany's executable.
+ *
+ * The path can be either an absolute path or just a filename ("geany") to be
+ * resolved using @c PATH.
+ *
+ * The value is used to construct Geany's restart command in @c sm_set_runtime_props().
+ * The variable is initialized in @c sm_store_props().
+ */
+static SmPropValue sm_program_val;
+
+/**
+ * @c SmPropValue storing a string of libSM client ID command-line option and its value.
+ *
+ * The value is used to construct Geany's restart command in @c sm_set_runtime_props().
+ * The variable is initialized in @c sm_store_props().
+ */
+static SmPropValue sm_client_id_arg_val;
+
+#endif
+
+/**
+ * @name Exported functions
+ * @{
+ */
+
+/**
+ * Initialize XSMP support.
+ *
+ * @param argv0             Value of @c argv[0] used to define Geany's restart command.
+ * @param libsm_client_id   Client-ID specified with "--libsm-client-id" command line
+ *                          option or @c NULL if the option was not passed.
+ *
+ * This function connects to the session manager using @c sm_connect(). If
+ * everything is successful, it stores libSM connection object in the global
+ * variable @c smcon and calls @c sm_store_props() and @c sm_set_contant_props().
+ *
+ * This function can be called at any time during Geany instance's lifetime.
+ * Actually it is called when Geany is starting.
+ *
+ * When Geany is compiled without XSMP support, this function is a no-op.
+ */
+void sm_init(const char * argv0, const char * libsm_client_id)
+{
+	#ifdef HAVE_LIBSM
+
+	char * new_client_id;
+	SmcConn smcon = sm_connect(libsm_client_id, &new_client_id);
+
+	if (!smcon)
+    return;
+
+  sm_store_props(argv0, new_client_id);
+  sm_set_constant_props(smcon);
+  free(new_client_id);
+  #endif
+}
+
+/** @} */
+
+#ifdef HAVE_LIBSM
+
+/**
+ * @name ICE support
+ * @{
+ */
+
+/**
+ * ICE connection watcher used to attach a GIOChannel to each ICE connection
+ * so that this connection can be handled in GTK main loop.
+ *
+ * @param icecon        ICE connection.
+ * @param client_data   Client data specified in @c IceAddConnectionWatch() function call.
+ *                      Currently it is not used.
+ * @param opening       Whether @c icecon is opening or closing.
+ * @param watch_data    A piece of data that can be set when @c icecon is opening and
+ *                      read when it is closing. We store GIOChannel watcher ID here.
+ *
+ * We attach @c ice_iochannel_watch GIOChannel watcher to every created GIOChannel
+ * in order to handle messages. This is how session manager communicates to Geany.
+ *
+ * @see sm_connect()
+ */
+static void ice_connection_watch(IceConn icecon, IcePointer client_data,
+		Bool opening, IcePointer *watch_data)
+{
+	guint input_id;
+
+	if (opening)
+	{
+		GIOChannel * channel = g_io_channel_unix_new(IceConnectionNumber(icecon));
+		input_id = g_io_add_watch(channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
+			ice_iochannel_watch, icecon);
+		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);
+	}
+}
+
+
+/**
+ * A watcher attached to a GIOChannel corresponding to an ICE connection.
+ *
+ * @param source   A GIOChannel corresponding to an ICE connection.
+ * @param condition
+ * @param data     Client data specified in @c g_io_add_watch() function call.
+ *                 An ICE connection object (of type @c IceConn) is stored here.
+ * @return         Return FALSE to remove the GIOChannel.
+ *
+ * This function calls @c IceProcessMessages causing an appropriate libSM
+ * callback to be invoked.
+ *
+ * @see ice_connection_watch()
+ */
+static gboolean ice_iochannel_watch(GIOChannel * source, GIOCondition condition, gpointer data)
+{
+	IceConn icecon = (IceConn)data;
+	IceProcessMessages(icecon, NULL, NULL);
+	return TRUE;
+}
+
+/**
+ * @}
+ * @name libSM support implementation
+ * @{
+ */
+
+/**
+ * Connect to the session manager.
+ *
+ * @param libsm_client_id      LibSM client ID saved from the previous session
+ *                             or @c NULL if there was no previous session.
+ * @param[out] new_client_id   Upon successful completion, this variable is
+ *                             filled with newly retrieved libSM client ID.
+ *                             The string should be freed with @c free() when
+ *                             it is no longer needed. If connection to the
+ *                             session manager fails, the variable is filled
+ *                             with @c NULL pointer.
+ * @return libSM connection object or @c 0 if connection to the session manager fails.
+ */
+static SmcConn sm_connect(const char * libsm_client_id, char ** new_client_id)
+{
+	static const SmcCallbacks callbacks = {
+		{sm_save_yourself_callback, NULL},
+		{sm_die_callback, NULL},
+		{sm_save_complete_callback, NULL},
+		{sm_shutdown_cancelled_callback, NULL}};
+
+	gchar err[256] = "";
+	SmcConn smcon;
+
+  if (!g_getenv("SESSION_MANAGER"))
+		return 0;
+
+	IceAddConnectionWatch(ice_connection_watch, NULL);
+
+	smcon = (gpointer)SmcOpenConnection(NULL, NULL,
+		SmProtoMajor, SmProtoMinor,
+		SmcSaveYourselfProcMask |
+		SmcDieProcMask |
+		SmcSaveCompleteProcMask |
+		SmcShutdownCancelledProcMask,
+		(SmcCallbacks *)&callbacks,
+		(char *)libsm_client_id, new_client_id,
+		256, err);
+
+	if (!smcon)
+	{
+		g_warning("While connecting to session manager:\n%s.", err);
+
+    IceRemoveConnectionWatch(ice_connection_watch, NULL);
+		return 0;
+	}
+
+  return smcon;
+}
+
+
+/**
+ * Store global @c SmPropValue objects.
+ *
+ * @param argv0             Value of @c argv[0].
+ * @param libsm_client_id   LibSM client ID saved from the previous session or
+ *                          @c NULL if there was no previous session.
+ *
+ * This function stores @c SmPropValue objects which are initialized using
+ * arguments passed to @c sm_init() and used in @c sm_set_runtime_props().
+ * The objects are stored in the global scope.
+ *
+ * Function parameters correspond to the parameters of @c sm_init().
+ *
+ * @see sm_client_id_arg_val
+ * @see sm_program_val
+ */
+static void sm_store_props(const char * argv0, const char * libsm_client_id)
+{
+	const char * executable_path;
+	const char * client_id_arg;
+
+		/*
+		 * If this instance is run with path (e.g., "./geany", "src/geany",
+		 * "/usr/local/bin/geany"), specify absolute path in restart command.
+		 * Relative paths may not work even if we set SmCurrentDirectory prop.
+		 *
+		 * Otherwise leave argv[0] as is (geany will be found
+		 * using PATH environment variable).
+		 */
+	if (strchr(argv0, G_DIR_SEPARATOR) == NULL)
+		executable_path = argv0;
+	else
+	{
+		gchar * curdir = g_get_current_dir();
+		executable_path = g_build_filename(curdir, argv0, NULL); /* never freed */
+		g_free(curdir);
+	}
+	sm_program_val.length = strlen(executable_path);
+	sm_program_val.value = (char *)executable_path;
+
+	client_id_arg = g_strconcat("--libsm-client-id=", libsm_client_id, NULL); /* never freed */
+	sm_client_id_arg_val.length = strlen(client_id_arg);
+	sm_client_id_arg_val.value = (char *)client_id_arg;
+}
+
+
+/**
+ * Set libSM properties which values are not changed during Geany's lifetime.
+ *
+ * @param smcon   LibSM connection object.
+ *
+ * This function can be called at any time during Geany's lifetime.
+ * Actually it is called from @c sm_init().
+ *
+ * The function requires @c SmPropValue objects stored in @c sm_store_props(),
+ * so the caller must call the latter function before it calls this one.
+ */
+static void sm_set_constant_props(SmcConn smcon)
+{
+	const gchar * username = g_get_user_name();
+	gchar * curdir = g_get_current_dir();
+
+	SmPropValue userid_val = {
+		username ? strlen(username) : 0,
+		username ? (char *)username : ""};
+	SmPropValue curdir_val = {
+		strlen(curdir),
+		curdir};
+
+	SmProp program_prop = {SmProgram, SmARRAY8, 1, &sm_program_val};
+	SmProp userid_prop = {SmUserID, SmARRAY8, 1, &userid_val};
+	SmProp curdir_prop = {SmCurrentDirectory, SmARRAY8, 1, &curdir_val};
+
+	SmProp * proplist[3] = {
+		&program_prop,
+		&userid_prop,
+		&curdir_prop
+	};
+	SmcSetProperties(smcon, 3, proplist);
+
+	g_free(curdir);
+}
+
+
+/**
+ * Set libSM properties which values can be modified during Geany's lifetime.
+ *
+ * @param smcon   LibSM connection object.
+ *
+ * This function should be called as late as possible in order to avoid a
+ * situation when values are modified after they are stored and the stored
+ * values become incorrect.
+ *
+ * The function requires @c SmPropValue objects stored in @c sm_store_props(),
+ * so the caller must call the latter function before it calls this one.
+ */
+static void sm_set_runtime_props(SmcConn smcon)
+{
+	/*
+	 * FIXME: We have to specify '--no-session' command-line argument in commands.
+	 *
+	 * Reason:
+	 *
+	 *   Currently all Geany instances try to save session. Consider the
+	 *   following use case.
+	 *
+	 *   User creates two instances of geany, a "main" one (for example, using
+	 *   the main menu of his DE) and a "non-main" one typing 'geany --new-instance'
+	 *   in a terminal emulator. When this user logouts, session manager sends
+	 *   termination messages to Geany instances in unpredictable order. Geany
+	 *   session will be saved by the instance that was last to handle the
+	 *   message. Suppose that was the "non-main" Geany instance.
+	 *
+	 *   When the user logins again, session manager restores Geany instances,
+	 *   again in unpredictable order. If we do not supply '--no-session' argument,
+	 *   the "main" instance will catch the session stored by the "non-main"
+	 *   one, which is not desired behaviour.
+	 *
+	 * Drawbacks of the '--no-session' solution:
+	 *
+	 *   The "main" instance won't save Geany session as required.
+	 *
+	 * Possible fixes:
+	 *
+	 *   * Disable saving of Geany session for "non-main" Geany instances.
+	 *     Sounds sensible and applicable.
+	 *
+	 *   * Save session even when '--no-session' option is specified (i.e.,
+	 *     consider this options only when reading Geany session). Sounds
+	 *     non-applicable as the described behaviour does not match
+	 *     even the option's name.
+	 *
+	 *   * Create a separate option to be used when reading Geany session
+	 *     is needed and writing Geany session is forbidden. Sounds awkward.
+	 */
+
+	const gint page_count = gtk_notebook_get_n_pages(GTK_NOTEBOOK(main_widgets.notebook));
+	gint page;
+
+	GArray * arr;
+	gint arr_real_len;
+
+	SmProp restart_command_prop, clone_command_prop;
+	SmPropValue * val;
+	SmProp * prop;
+
+
+		/*
+		 * Allocate space for `page_count+4' elements:
+		 *   * tree elements for program name, client ID and '--no-session' option;
+		 *   * possibly one element for '--new-instance' option;
+		 *   * max `page_count' elements for file paths.
+		 * Store the number of actually used elements in `arr_real_len'.
+		 */
+	arr = g_array_sized_new(FALSE, FALSE, sizeof(SmPropValue), page_count+4);
+	arr_real_len = 0;
+
+	((SmPropValue *)arr->data)[0] = sm_program_val;
+	((SmPropValue *)arr->data)[1] = sm_client_id_arg_val;
+	((SmPropValue *)arr->data)[2].length = 2; /* length of "-s" */
+	((SmPropValue *)arr->data)[2].value = "-s";
+	arr_real_len = 3;
+
+#ifdef HAVE_SOCKET
+	if (cl_options.new_instance)
+	{
+		val = ((SmPropValue *)arr->data) + arr_real_len++;
+		val->length = 2; /* length of "-i" */
+		val->value = "-i";
+	}
+#endif
+	/* TODO: handle other command-line options */
+
+	for (page = 0; page < page_count; page++)
+	{
+		GeanyDocument * doc = document_get_from_page(page);
+		if (doc->real_path)
+		{
+			val = ((SmPropValue *)arr->data) + arr_real_len++;
+			val->length = strlen(doc->real_path);
+			val->value = doc->real_path;
+		}
+	}
+
+	restart_command_prop.name = SmRestartCommand;
+	restart_command_prop.type = SmLISTofARRAY8;
+	restart_command_prop.num_vals = arr_real_len;
+	restart_command_prop.vals = (SmPropValue *)arr->data;
+	prop = &restart_command_prop;
+	SmcSetProperties(smcon, 1, &prop);
+
+		/*
+		 * We should not specify client ID in SmCloneCommand,
+		 * so "remove" the corresponding element from `arr'.
+		 */
+	((SmPropValue *)arr->data)[1] = sm_program_val;
+
+	clone_command_prop.name = SmCloneCommand;
+	clone_command_prop.type = SmLISTofARRAY8;
+	clone_command_prop.num_vals = arr_real_len - 1;
+	clone_command_prop.vals = (SmPropValue *)arr->data + 1;
+	prop = &clone_command_prop;
+	SmcSetProperties(smcon, 1, &prop);
+
+	g_array_free(arr, TRUE);
+}
+
+/**
+ * @}
+ * @name libSM callbacks
+ * @{
+ */
+
+/**
+ * "Save Yourself" callback.
+ *
+ * @param smcon            LibSM connection object.
+ * @param client_data      Client data specified when the callback was registered.
+ *                         Currently it is not used.
+ * @param save_type 	     Specifies the type of information that should be saved.
+ * @param shutdown 	       Specifies if a shutdown is taking place.
+ * @param interact_style 	 The type of interaction allowed with the user.
+ * @param fast 	           If True, the client should save its state as quickly as possible.
+ *
+ * See libSM documentation for more details.
+ *
+ * Here we save Geany's configuration. If there are any unsaved documents and
+ * interaction with the user is allowed, we also make an "Interact Request" so
+ * that session manager sends us an "Interact" message. Otherwise, we are ready
+ * to handle "Die" or "Shutdown Cancelled" message.
+ *
+ * @see sm_interact_callback()
+ * @see sm_shutdown_cancelled_callback()
+ * @see sm_die_callback()
+ */
+static void sm_save_yourself_callback(SmcConn smcon, SmPointer client_data,
+		int save_type, Bool shutdown, int interact_style, Bool fast)
+{
+	if (save_type == SmSaveGlobal || save_type == SmSaveBoth)
+	{
+		if (interact_style == SmInteractStyleAny && document_any_unsaved())
+		{
+			if (!SmcInteractRequest(smcon, SmDialogNormal, sm_interact_callback, NULL))
+				SmcSaveYourselfDone(smcon, FALSE);
+			return;
+		}
+		else
+			main_save(FALSE);
+	}
+
+	/*
+	 * TODO: libSM documentation says that when the system is being shutting down
+	 * (`shutdown' argument is True), we should disable user interaction here
+	 * until we get "Die" or "Shutdown" message.
+	 */
+
+	sm_set_runtime_props(smcon);
+	SmcSaveYourselfDone(smcon, TRUE);
+}
+
+
+/**
+ * "Interact" callback.
+ *
+ * @param smcon         LibSM connection object.
+ * @param client_data   Client data specified when the callback was registered.
+ *                      Currently it is not used.
+ *
+ * See libSM documentation for more details.
+ *
+ * The session manager sends us an "Interact" message after we make an
+ * "Interact Request" in @c sm_save_yourself_callback(). Here we are allowed to
+ * interact with the user, so we ask her whether to save changed documents
+ * (the user also can cancel the shutdown). After that we are ready to handle
+ * "Shutdown Cancelled" or "Die" message.
+ *
+ * @see sm_shutdown_cancelled_callback()
+ * @see sm_die_callback()
+ */
+static void sm_interact_callback(SmcConn smcon, SmPointer client_data)
+{
+	gboolean cancelled = !main_save(TRUE);
+	sm_set_runtime_props(smcon);
+	SmcInteractDone(smcon, cancelled);
+}
+
+
+/**
+ * "Save Complete" callback.
+ *
+ * @param smcon         LibSM connection object.
+ * @param client_data   Client data specified when the callback was registered.
+ *                      Currently it is not used.
+ *
+ * See libSM documentation for more details.
+ *
+ * We are required to have this callback, but actually it is a no-op.
+ */
+static void sm_save_complete_callback(SmcConn smcon, SmPointer client_data)
+{
+}
+
+
+/**
+ * "Shutdown Cancelled" callback.
+ *
+ * @param smcon         LibSM connection object.
+ * @param client_data   Client data specified when the callback was registered.
+ *                      Currently it is not used.
+ *
+ * See libSM documentation for more details.
+ *
+ * We are required to have this callback, but actually it is a no-op.
+ */
+static void sm_shutdown_cancelled_callback(SmcConn smcon, SmPointer client_data)
+{
+	SmcSaveYourselfDone(smcon, TRUE);
+}
+
+
+/**
+ * "Die" callback.
+ *
+ * @param smcon         LibSM connection object.
+ * @param client_data   Client data specified when the callback was registered.
+ *                      Currently it is not used.
+ *
+ * See libSM documentation for more details.
+ *
+ * The session manager asks us to quit Geany and we do it.
+ */
+static void sm_die_callback(SmcConn smcon, SmPointer client_data)
+{
+	SmcCloseConnection(smcon, 0, NULL);
+	main_finalize();
+}
+
+/** @} */
+
+#endif
diff --git a/src/sm.h b/src/sm.h
new file mode 100644
index 0000000..563c249
--- /dev/null
+++ b/src/sm.h
@@ -0,0 +1,28 @@
+/*
+ *      sm.h - this file is part of Geany, a fast and lightweight IDE
+ *
+ *      Copyright 2009 Eugene Arshinov <earshinov(at)gmail(dot)com>
+ *
+ *      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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $Id$
+ */
+
+#ifndef GEANY_SM_H
+#define GEANY_SM_H 1
+
+void sm_init(const char * argv0, const char * libsm_client_id);
+
+#endif
diff --git a/wscript b/wscript
index b219bd2..0e102a5 100644
--- a/wscript
+++ b/wscript
@@ -109,7 +109,7 @@ geany_sources = [
 	'src/highlighting.c', 'src/interface.c', 'src/keybindings.c',
 	'src/keyfile.c', 'src/log.c', 'src/main.c', 'src/msgwindow.c', 'src/navqueue.c', 'src/notebook.c',
 	'src/plugins.c', 'src/pluginutils.c', 'src/prefix.c', 'src/prefs.c', 'src/printing.c', 'src/project.c',
-	'src/sciwrappers.c', 'src/search.c', 'src/socket.c', 'src/stash.c',
+	'src/sciwrappers.c', 'src/search.c', 'src/sm.c', 'src/socket.c', 'src/stash.c',
 	'src/symbols.c',
 	'src/templates.c', 'src/toolbar.c', 'src/tools.c', 'src/sidebar.c',
 	'src/ui_utils.c', 'src/utils.c' ]
-- 
1.6.5.5

