diff --git a/src/main.c b/src/main.c
index 8055dc8..4a3ee32 100644
--- a/src/main.c
+++ b/src/main.c
@@ -83,6 +83,11 @@
 # 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
@@ -115,6 +120,7 @@ static gboolean print_prefix = FALSE;
 static gboolean no_plugins = FALSE;
 #endif
 static gboolean dummy = FALSE;
+static gchar * libsm_client_id = NULL;
 
 /* in alphabetical order of short options */
 static GOptionEntry entries[] =
@@ -143,10 +149,302 @@ static GOptionEntry entries[] =
 	{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_mode, N_("Be verbose"), NULL },
 	{ "version", 'V', 0, G_OPTION_ARG_NONE, &show_version, N_("Show version and exit"), NULL },
 	{ "dummy", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &dummy, NULL, NULL }, /* for +NNN line number arguments */
+	{ "libsm-client-id", 0, G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &libsm_client_id, NULL, NULL },
 	{ NULL, 0, 0, 0, NULL, NULL, NULL }
 };
 
 
+#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().
+	 */
+
+		/* These strings are stored in global variables and are never free'd */
+	gchar * client_id_arg = g_strconcat("--libsm-client-id=", client_id, NULL);
+	gchar * executable_path = g_build_filename(curdir, argv0, NULL);
+
+	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 */
@@ -919,6 +1217,10 @@ gint main(gint argc, gchar **argv)
 	}
 #endif
 
+#ifdef HAVE_LIBSM
+	sm_init(argv[0]);
+#endif
+
 	geany_debug("Geany %s, GTK+ %u.%u.%u, GLib %u.%u.%u",
 		main_get_version_string(),
 		gtk_major_version, gtk_minor_version, gtk_micro_version,
