Hi all.
The attached patches implement X session management protocol (XSMP)
support via libSM library.
Why XSMP?
When a user logs out with some instances of Geany running, XSMP
support will allow those instances to safely save their
configuration and gracefully ask the user about unsaved documents. The
logout process will halt until Geany instances report that they are
ready for shutdown.
When the user logs in, all Geany instances will be restored, with
the same documents open. XSMP allows us to set a restart command for
each Geany instance, so I just remember open documents by passing
their file paths via command-line arguments.
Why libSM?
This library is as lightweight as possible. AFAIK, it is written by
the authors of Xorg. It is also very popular, used in claws-mail and
required by GNOME.
Patches:
[1.autotools.patch]
Building with X session management support if libSM library is
present. Is the library is not installed or ./configure was run
with `--enable-libsm=no', XSMP support is disabled.
I tested three cases:
* libSM installed;
* libSM installed, --enable-libsm=no;
* libSM is not installed.
[2.waf.patch]
The same thing, but using waf instead of autotools. It is
possible to disable XSMP support by passing `--disable-libsm'
to `./waf congigure'.
Again, I tested three cases:
* libSM installed;
* libSM installed, --disable-libsm supplied;
* libSM is not installed.
[3.refactor.patch]
This is probably the most doubtful part. In order to support XSMP
properly, I need:
1) a non-destructive function to completely save Geany's state;
2) a function to quit Geany without saving anything and
interacting with user.
I found `main_quit' function to satisfy the (2) requirements. But
there was no function to satisfy (1), so I created a new one called
`main_save' from `quit_app' function located in [callbacks.c].
I also had to
* add `force' argument to `document_close_all' function
to be used when you do not want the function to ask about
unsaved documents;
* create a trivial `project_save' function; add `save_config'
argument to `project_close' function so that I can set it to
FALSE when I already called `project_save'.
I wanted to reuse existing static `check_no_unsaved' function
located in [callbacks.c], so I created new `document_any_unsaved'
function from it.
I renamed `main_quit' function to `main_finalize' in order to point
out that it does not interact with user and does not save anything
(yes, it's doubtful and I'll change it back if you wish).
As far as I understand, none of the changed functions are exported
for plugins, so the changes do not break API or ABI compatibility.
[4.implementation.patch]
The implementation. I did not to extract it to separate source
code files so far, but I'll definitely do it if you wish.
XSMP requires a Geany instance to have the same XSMP client-ID
when it is restarted by the session manager. I created new
`--libsm-client-id' command-line option in order to specify it
in restart command.
Actually, I looked into claws-mail source code and did not find any
code to maintain client-ID there. Maybe maintaining client-ID is
not very important, so I can remove `--libsm-client-id' option if
anyone votes against it.
Problems:
1. Geany session management
I have to use `--no-session' command-line option in restart
command. Please see code comments inside [4.implementation.patch],
the "FIXME" section. There I described, why I have to use the
option and why it is bad.
There is an easy fix: Geany instance should not save Geany session
if the instance is run with `--new-instance' option. I consider
this behaviour acceptable.
If all geany-devel subscribers agree, we should ask geany-users. If
they agree too, anyone can write the necessary code, and I will
change the handling of `--no-session' in my [4.implementation.patch].
Untested functionality:
1. Building on Windows
I had no opportunity to test building on Windows. Autotools and waf
should simply fail to find libsm, thus XSMP support should be
disabled.
TODO:
1. Handle all command-line options
Most of command-line options, specified when running Geany, should
go to restart command. Things get little complicated as some
options need special handling (for example, I think that `--line'
and `--column' options should not go to restart command).
There is a little problem with this task: if I write all
command-line handling code in my `sm_set_command_props' function,
there will be code duplication between `sm_set_command_props'
implementation and the array of GOptionEntry. Every time when a new
command-line option is added, `sm_set_command_props' will have to
be changed, particularly:
* the name of the variable, where the option's value is stored,
will have to be duplicated there;
* the handling of the option will depend on option's type (int,
string, etc.), which is directly corresponds to the type
specified in the `entries' array.
I think, the best solution of this code duplication problem is some
kind of a "reverse" parser of GOptionEntry's. It does not make
sense to write one when you have 10 options or so, most of which
have string values. But if such a "reverse" parser existed, I would
consider using it. Information about whether a particular option
should/shouldn't go to restart command could be placed in a
separate array near `entries'.
Maybe write a plugin?
Yes, it is possible to write a plugin instead of building XSMP
support straight into Geany. The plugin would require:
* some data when initializing (argv[0] and the value of
`--libsm-client-id' command-line option);
* access to `cl_options' struct to set restart command properly;
* access to `main_save' and `main_finalize' functions
in order to control Geany.
A bad thing about this is that we'll still have to hardcode
`--libsm-client-id' command-line option in Geany's source code,
even with XSMP support residing in a plugin.
Summary of questions
* Are there anyone who thinks that `--new-instance's should save
Geany session?
* Are there any "reverse" parser of GOptionEntries?
* Should I write a plugin? If not, should I extract the code into
separate source code files?
Hope you find it useful. Any thoughts?
Best regards,
Eugene.diff --git a/configure.in b/configure.in
index d08c709..a2fcd7e 100644
--- a/configure.in
+++ b/configure.in
@@ -249,6 +249,26 @@ if test "x$MSGFMT" = "xno"; then
fi
+# libSM for X session management
+SM_LIBS=""
+AC_ARG_ENABLE(libsm,
+ [ --enable-libsm enable X session management support [[]]],
+ [enable_libsm=$enableval], [enable_libsm=yes])
+AC_MSG_CHECKING([whether to use LibSM])
+if test x"$enable_libsm" = xyes; then
+ AC_MSG_RESULT(yes)
+ AC_CHECK_HEADERS(X11/SM/SMlib.h, [SM_LIBS="-lSM -lICE"], enable_libsm=no)
+ if test x"$enable_libsm" = xyes; then
+ AC_DEFINE(HAVE_LIBSM, 1, [Define to 1 if you have libSM installed])
+ else
+ AC_MSG_WARN([X session management will not be supported])
+ fi
+else
+ AC_MSG_RESULT(no)
+fi
+AC_SUBST(SM_LIBS)
+
+
# Set ${datadir}
if test "x${datadir}" = 'x${prefix}/share' -o "x${datarootdir}" = 'x${prefix}/share'; then
if test "x${prefix}" = "xNONE"; then
@@ -292,20 +312,21 @@ doc/Doxyfile
])
echo "----------------------------------------"
-echo "Install Geany in : ${prefix}"
+echo "Install Geany in : ${prefix}"
if test "x${build}" != "x" -a "x${target}" != "x"
then
- echo "Building Geany on : ${build}"
- echo "Building Geany for : ${target}"
+ echo "Building Geany on : ${build}"
+ echo "Building Geany for : ${target}"
fi
-echo "Using GTK version : ${GTK_VERSION}"
-echo "Build with GTK printing support : ${enable_printing}"
-echo "Build with plugin support : ${enable_plugins}"
-echo "Use virtual terminal support : ${want_vte}"
-echo "Use (UNIX domain) socket support : ${want_socket}"
+echo "Using GTK version : ${GTK_VERSION}"
+echo "Build with GTK printing support : ${enable_printing}"
+echo "Build with plugin support : ${enable_plugins}"
+echo "Build with X session management support : ${enable_libsm}"
+echo "Use virtual terminal support : ${want_vte}"
+echo "Use (UNIX domain) socket support : ${want_socket}"
if test "${REVISION}" != "-1"
then
- echo "Compiling Subversion revision : ${REVISION}"
+ echo "Compiling Subversion revision : ${REVISION}"
fi
echo ""
echo "Configuration is done OK."
diff --git a/src/Makefile.am b/src/Makefile.am
index df765c4..9f9b276 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -110,7 +110,7 @@ else
geany_SOURCES = $(SRCS) vte.c vte.h
-geany_LDADD = ../scintilla/libscintilla.a ../tagmanager/libtagmanager.a @GTK_LIBS@ @GIO_LIBS@ $(INTLLIBS)
+geany_LDADD = ../scintilla/libscintilla.a ../tagmanager/libtagmanager.a @GTK_LIBS@ @GIO_LIBS@ @SM_LIBS@ $(INTLLIBS)
AM_CFLAGS = -DGEANY_DATADIR=\""$(datadir)"\" \
-DGEANY_DOCDIR=\""$(docdir)"\" \
diff --git a/wscript b/wscript
index 0fccc4b..812ea46 100644
--- a/wscript
+++ b/wscript
@@ -211,6 +211,12 @@ def configure(conf):
gtk_version = 'Unknown'
conf.check_cfg(package='gio-2.0', uselib_store='GIO', args='--cflags --libs', mandatory=False)
+ # Find libSM
+ if not Options.options.no_libsm:
+ sm_version = conf.check_cfg(package='sm', uselib_store='SM', args='--cflags --libs', mandatory=False)
+ if sm_version is None:
+ Options.options.no_libsm = True
+
# Windows specials
if is_win32:
if conf.env['PREFIX'] == tempfile.gettempdir():
@@ -259,6 +265,7 @@ def configure(conf):
conf_define_from_opt('HAVE_PLUGINS', not Options.options.no_plugins, None, 0)
conf_define_from_opt('HAVE_SOCKET', not Options.options.no_socket, None, 0)
conf_define_from_opt('HAVE_VTE', not Options.options.no_vte, None, 0)
+ conf_define_from_opt('HAVE_LIBSM', not Options.options.no_libsm, None, 0)
conf.write_config_header('config.h')
@@ -267,6 +274,7 @@ def configure(conf):
print_message(conf, 'Using GTK version', gtk_version)
print_message(conf, 'Build with GTK printing support', have_gtk_210 and 'yes' or 'no')
print_message(conf, 'Build with plugin support', Options.options.no_plugins and 'no' or 'yes')
+ print_message(conf, 'Build with X session management support', Options.options.no_libsm and 'no' or 'yes')
print_message(conf, 'Use virtual terminal support', Options.options.no_vte and 'no' or 'yes')
if svn_rev != '-1':
print_message(conf, 'Compiling Subversion revision', svn_rev)
@@ -295,6 +303,9 @@ def set_options(opt):
opt.add_option('--disable-vte', action='store_true', default=target_is_win32(os.environ),
help='compile without support for an embedded virtual terminal [[default: No]',
dest='no_vte')
+ opt.add_option('--disable-libsm', action='store_true', default=target_is_win32(os.environ),
+ help='compile without X session management support [[default: No]',
+ dest='no_libsm')
opt.add_option('--enable-gnu-regex', action='store_true', default=False,
help='compile with included GNU regex library [default: No]', dest='gnu_regex')
# Paths
@@ -370,7 +381,7 @@ def build(bld):
target = 'geany',
source = geany_sources,
includes = '. src/ scintilla/include/ tagmanager/include/',
- uselib = 'GTK GIO WIN32',
+ uselib = 'GTK GIO SM WIN32',
uselib_local = 'scintilla tagmanager',
add_objects = 'geany-rc' if is_win32 else None
)
diff --git a/src/callbacks.c b/src/callbacks.c
index db5154e..b23de37 100644
--- a/src/callbacks.c
+++ b/src/callbacks.c
@@ -90,21 +90,6 @@ static gboolean insert_callback_from_menu = FALSE;
/*static gboolean switch_tv_notebook_page = FALSE; */
-static gboolean check_no_unsaved(void)
-{
- guint i;
-
- for (i = 0; i < documents_array->len; i++)
- {
- if (documents[i]->is_valid && documents[i]->changed)
- {
- return FALSE;
- }
- }
- return TRUE; /* no unsaved edits */
-}
-
-
/* set editor_info.click_pos to the current cursor position if insert_callback_from_menu is TRUE
* to prevent invalid cursor positions which can cause segfaults */
static void verify_click_pos(GeanyDocument *doc)
@@ -117,42 +102,26 @@ static void verify_click_pos(GeanyDocument *doc)
}
-/* should only be called from on_exit_clicked */
-static void quit_app(void)
-{
- configuration_save();
-
- if (app->project != NULL)
- project_close(FALSE); /* save project session files */
-
- document_close_all();
-
- main_status.quitting = TRUE;
-
- main_quit();
-}
-
-
/* wrapper function to abort exit process if cancel button is pressed */
gboolean
on_exit_clicked (GtkWidget *widget, gpointer gdata)
{
main_status.quitting = TRUE;
- if (! check_no_unsaved())
+ gboolean quit = TRUE;
+ if (!document_any_unsaved() && prefs.confirm_exit)
{
- if (document_account_for_unsaved())
- {
- quit_app();
- return FALSE;
- }
+ /*
+ * Ask for confirmation only if there are no unsaved documents.
+ * If any documents are unsaved, user will be asked in main_save().
+ */
+ if (!dialogs_show_question_full(NULL, GTK_STOCK_QUIT, GTK_STOCK_CANCEL, NULL, _("Do you really want to quit?")))
+ quit = FALSE;
}
- else
- if (! prefs.confirm_exit ||
- dialogs_show_question_full(NULL, GTK_STOCK_QUIT, GTK_STOCK_CANCEL, NULL,
- _("Do you really want to quit?")))
+
+ if (quit && main_save(TRUE))
{
- quit_app();
+ main_finalize();
return FALSE;
}
@@ -239,7 +208,7 @@ void
on_close_all1_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- document_close_all();
+ document_close_all(FALSE);
}
@@ -1824,7 +1793,7 @@ void
on_project_close1_activate (GtkMenuItem *menuitem,
gpointer user_data)
{
- project_close(TRUE);
+ project_close(TRUE, TRUE);
}
diff --git a/src/document.c b/src/document.c
index b3471ea..04facea 100644
--- a/src/document.c
+++ b/src/document.c
@@ -2850,6 +2850,17 @@ GeanyDocument *document_clone(GeanyDocument *old_doc, const gchar *utf8_filename
}
+/* @return TRUE if there are some unsaved documents */
+gboolean document_any_unsaved(void)
+{
+ guint i;
+ foreach_document(i)
+ if (documents[i]->changed)
+ return TRUE;
+ return FALSE;
+}
+
+
/* @note If successful, this should always be followed up with a call to
* document_close_all().
* @return TRUE if all files were saved or had their changes discarded. */
@@ -2904,13 +2915,12 @@ static void force_close_all(void)
}
-gboolean document_close_all(void)
+gboolean document_close_all(gboolean force)
{
- if (! document_account_for_unsaved())
+ if (!force && !document_account_for_unsaved())
return FALSE;
force_close_all();
-
return TRUE;
}
diff --git a/src/document.h b/src/document.h
index 541b49a..31a86b4 100644
--- a/src/document.h
+++ b/src/document.h
@@ -196,9 +196,11 @@ void document_try_focus(GeanyDocument *doc);
gboolean document_close(GeanyDocument *doc);
+gboolean document_any_unsaved(void);
+
gboolean document_account_for_unsaved(void);
-gboolean document_close_all(void);
+gboolean document_close_all(gboolean force);
GeanyDocument *document_clone(GeanyDocument *old_doc, const gchar *utf8_filename);
diff --git a/src/main.c b/src/main.c
index b78acb9..8055dc8 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1066,10 +1066,28 @@ static void queue_free(GQueue *queue)
}
-void main_quit()
+gboolean main_save(gboolean interactive)
+{
+ if (interactive && !document_account_for_unsaved())
+ return FALSE;
+
+ configuration_save();
+
+ if (app->project)
+ project_save(FALSE);
+
+ return TRUE;
+}
+
+
+void main_finalize()
{
geany_debug("Quitting...");
+ if (app->project)
+ project_close(FALSE, FALSE);
+ document_close_all(TRUE);
+
#ifdef HAVE_SOCKET
socket_finalize();
#endif
diff --git a/src/main.h b/src/main.h
index 3c4f41a..b5a6716 100644
--- a/src/main.h
+++ b/src/main.h
@@ -56,7 +56,9 @@ const gchar *main_get_version_string(void);
gchar *main_get_argv_filename(const gchar *filename);
-void main_quit(void);
+gboolean main_save(gboolean interactive);
+
+void main_finalize(void);
gboolean main_handle_filename(const gchar *locale_filename);
diff --git a/src/project.c b/src/project.c
index 6a6cf7e..07e311b 100644
--- a/src/project.c
+++ b/src/project.c
@@ -335,15 +335,22 @@ static void remove_foreach_project_filetype(gpointer data, gpointer user_data)
}
+void project_save(gboolean emit_signal)
+{
+ write_config(emit_signal);
+}
+
+
/* open_default will make function reload default session files on close */
-void project_close(gboolean open_default)
+void project_close(gboolean save_config, gboolean open_default)
{
g_return_if_fail(app->project != NULL);
ui_set_statusbar(TRUE, _("Project \"%s\" closed."), app->project->name);
/* use write_config() to save project session files */
- write_config(FALSE);
+ if (save_config)
+ write_config(FALSE);
/* remove project filetypes build entries */
if (app->project->build_filetypes_list != NULL)
@@ -370,7 +377,7 @@ void project_close(gboolean open_default)
if (project_prefs.project_session)
{
/* close all existing tabs first */
- document_close_all();
+ document_close_all(FALSE);
/* after closing all tabs let's open the tabs found in the default config */
if (open_default && cl_options.load_session)
@@ -601,7 +608,7 @@ gboolean project_ask_close(void)
_("Do you want to close it before proceeding?"),
_("The '%s' project is already open."), app->project->name))
{
- project_close(FALSE);
+ project_close(TRUE, FALSE);
return TRUE;
}
else
@@ -976,7 +983,7 @@ static gboolean load_config(const gchar *filename)
/* save current (non-project) session (it could has been changed since program startup) */
configuration_save_default_session();
/* now close all open files */
- document_close_all();
+ document_close_all(FALSE);
/* read session files so they can be opened with configuration_open_files() */
configuration_load_session_files(config, FALSE);
}
diff --git a/src/project.h b/src/project.h
index beccb2f..f72cbf6 100644
--- a/src/project.h
+++ b/src/project.h
@@ -68,7 +68,9 @@ void project_new(void);
void project_open(void);
-void project_close(gboolean open_default);
+void project_save(gboolean emit_signal);
+
+void project_close(gboolean save_config, gboolean open_default);
void project_properties(void);
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,
_______________________________________________
Geany-devel mailing list
[email protected]
http://lists.uvena.de/cgi-bin/mailman/listinfo/geany-devel