Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorMarco Pesenti Gritti <marco@localhost.localdomain>2008-06-06 17:13:10 (GMT)
committer Marco Pesenti Gritti <marco@localhost.localdomain>2008-06-06 17:13:10 (GMT)
commit50f79e6323eac5866b1e9793baa21f5db98aba2d (patch)
tree4d0d4acad3c224a310f9dc1b0b235502ac2cd874 /src
parent5ba50d0e0f7dc2342e2eabf56b73b4869c8e6a3a (diff)
First go at session management. Not asking review since
it's a bunch of ugly C code imported from gnome-session. Will clean it up a bit but I don't plan to make it too shiny :)
Diffstat (limited to 'src')
-rw-r--r--src/sugar/Makefile.am22
-rw-r--r--src/sugar/_sugarext.defs141
-rw-r--r--src/sugar/_sugarext.override5
-rw-r--r--src/sugar/activity/activity.py13
-rw-r--r--src/sugar/app.c396
-rw-r--r--src/sugar/app.h70
-rw-r--r--src/sugar/client-xsmp.c828
-rw-r--r--src/sugar/client-xsmp.h70
-rw-r--r--src/sugar/client.c251
-rw-r--r--src/sugar/client.h111
-rw-r--r--src/sugar/eggdesktopfile.c1437
-rw-r--r--src/sugar/eggdesktopfile.h156
-rw-r--r--src/sugar/eggsmclient-private.h54
-rw-r--r--src/sugar/eggsmclient-xsmp.c1359
-rw-r--r--src/sugar/eggsmclient.c392
-rw-r--r--src/sugar/eggsmclient.h112
-rw-r--r--src/sugar/session.c550
-rw-r--r--src/sugar/session.h93
-rw-r--r--src/sugar/session.py46
-rw-r--r--src/sugar/xsmp.c537
-rw-r--r--src/sugar/xsmp.h29
21 files changed, 6670 insertions, 2 deletions
diff --git a/src/sugar/Makefile.am b/src/sugar/Makefile.am
index e45ead9..a0e2e1d 100644
--- a/src/sugar/Makefile.am
+++ b/src/sugar/Makefile.am
@@ -5,6 +5,7 @@ sugar_PYTHON = \
env.py \
network.py \
profile.py \
+ session.py \
util.py \
wm.py
@@ -17,13 +18,27 @@ _sugarext_la_CFLAGS = \
$(PYTHON_INCLUDES)
_sugarext_la_LDFLAGS = -module -avoid-version
-_sugarext_la_LIBADD = $(EXT_LIBS)
+_sugarext_la_LIBADD = $(EXT_LIBS) -lSM -lICE
_sugarext_la_SOURCES = \
$(BUILT_SOURCES) \
_sugarextmodule.c \
+ app.h \
+ app.c \
+ client.h \
+ client.c \
+ client-xsmp.h \
+ client-xsmp.c \
eggaccelerators.c \
eggaccelerators.h \
+ eggdesktopfile.h \
+ eggdesktopfile.c \
+ eggsmclient.h \
+ eggsmclient.c \
+ eggsmclient-private.h \
+ eggsmclient-xsmp.c \
+ session.h \
+ session.c \
sexy-icon-entry.h \
sexy-icon-entry.c \
sugar-address-entry.c \
@@ -33,7 +48,9 @@ _sugarext_la_SOURCES = \
sugar-menu.h \
sugar-menu.c \
sugar-preview.h \
- sugar-preview.c
+ sugar-preview.c \
+ xsmp.h \
+ xsmp.c
BUILT_SOURCES = \
_sugarext.c \
@@ -46,6 +63,7 @@ _sugarext.c: _sugarext.defs _sugarext.override
(cd $(srcdir)\
&& $(PYGTK_CODEGEN) \
--register $(PYGTK_DEFSDIR)/gdk-types.defs \
+ --register $(PYGTK_DEFSDIR)/gdk-types.defs \
--register $(PYGTK_DEFSDIR)/gtk-types.defs \
--override $*.override \
--prefix py$* $*.defs) > gen-$*.c \
diff --git a/src/sugar/_sugarext.defs b/src/sugar/_sugarext.defs
index 1c9812e..02b673c 100644
--- a/src/sugar/_sugarext.defs
+++ b/src/sugar/_sugarext.defs
@@ -36,6 +36,27 @@
(gtype-id "SEXY_TYPE_ICON_ENTRY")
)
+(define-object SMClientXSMP
+ (in-module "Egg")
+ (parent "EggSMClient")
+ (c-name "EggSMClientXSMP")
+ (gtype-id "EGG_TYPE_SM_CLIENT_XSMP")
+)
+
+(define-object SMClient
+ (in-module "Egg")
+ (parent "GObject")
+ (c-name "EggSMClient")
+ (gtype-id "EGG_TYPE_SM_CLIENT")
+)
+
+(define-object Session
+ (in-module "Gsm")
+ (parent "GObject")
+ (c-name "GsmSession")
+ (gtype-id "GSM_TYPE_SESSION")
+)
+
;; Enumerations and flags ...
(define-enum IconEntryPosition
@@ -194,3 +215,123 @@
(c-name "sugar_preview_get_pixbuf")
(return-type "GdkPixbuf*")
)
+
+;; From eggsmclient.h
+
+(define-function egg_sm_client_get_type
+ (c-name "egg_sm_client_get_type")
+ (return-type "GType")
+)
+
+(define-function egg_sm_client_get_option_group
+ (c-name "egg_sm_client_get_option_group")
+ (return-type "GOptionGroup*")
+)
+
+(define-method is_resumed
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_is_resumed")
+ (return-type "gboolean")
+)
+
+(define-method get_state_file
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_get_state_file")
+ (return-type "GKeyFile*")
+)
+
+(define-method set_restart_command
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_set_restart_command")
+ (return-type "none")
+ (parameters
+ '("int" "argc")
+ '("const-char**" "argv")
+ )
+)
+
+(define-method startup
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_startup")
+ (return-type "none")
+)
+
+(define-method will_quit
+ (of-object "EggSMClient")
+ (c-name "egg_sm_client_will_quit")
+ (return-type "none")
+ (parameters
+ '("gboolean" "will_quit")
+ )
+)
+
+(define-function egg_sm_client_end_session
+ (c-name "egg_sm_client_end_session")
+ (return-type "gboolean")
+ (parameters
+ '("EggSMClientEndStyle" "style")
+ '("gboolean" "request_confirmation")
+ )
+)
+
+;; From xsmp.h
+
+(define-function xsmp_init
+ (c-name "gsm_xsmp_init")
+ (return-type "char*")
+)
+
+(define-function xsmp_run
+ (c-name "gsm_xsmp_run")
+ (return-type "none")
+)
+
+(define-function xsmp_shutdown
+ (c-name "gsm_xsmp_shutdown")
+ (return-type "none")
+)
+
+;; From session.h
+
+(define-method set_name
+ (of-object "GsmSession")
+ (c-name "gsm_session_set_name")
+ (return-type "none")
+ (parameters
+ '("const-char*" "name")
+ )
+)
+
+(define-method start
+ (of-object "GsmSession")
+ (c-name "gsm_session_start")
+ (return-type "none")
+)
+
+(define-method get_phase
+ (of-object "GsmSession")
+ (c-name "gsm_session_get_phase")
+ (return-type "GsmSessionPhase")
+)
+
+(define-method initiate_shutdown
+ (of-object "GsmSession")
+ (c-name "gsm_session_initiate_shutdown")
+ (return-type "none")
+)
+
+(define-method register_client
+ (of-object "GsmSession")
+ (c-name "gsm_session_register_client")
+ (return-type "char*")
+ (parameters
+ '("GsmClient*" "client")
+ '("const-char*" "previous_id")
+ )
+)
+
+(define-function session_create_global
+ (c-name "gsm_session_create_global")
+ (return-type "GsmSession*")
+)
+
diff --git a/src/sugar/_sugarext.override b/src/sugar/_sugarext.override
index 1d0289e..271edce 100644
--- a/src/sugar/_sugarext.override
+++ b/src/sugar/_sugarext.override
@@ -9,6 +9,11 @@ headers
#include "sugar-menu.h"
#include "sugar-preview.h"
#include "sexy-icon-entry.h"
+#include "session.h"
+
+#define EGG_SM_CLIENT_BACKEND_XSMP
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
#include <pygtk/pygtk.h>
#include <glib.h>
diff --git a/src/sugar/activity/activity.py b/src/sugar/activity/activity.py
index 6204354..3d3b995 100644
--- a/src/sugar/activity/activity.py
+++ b/src/sugar/activity/activity.py
@@ -69,6 +69,7 @@ from sugar.graphics.toolcombobox import ToolComboBox
from sugar.graphics.alert import Alert
from sugar.graphics.icon import Icon
from sugar.datastore import datastore
+from sugar.session import XSMPClient
from sugar import wm
from sugar import profile
from sugar import _sugarext
@@ -435,6 +436,11 @@ class Activity(Window, gtk.Container):
self._max_participants = 0
self._invites_queue = []
+ self._xsmp_client = XSMPClient()
+ self._xsmp_client.connect('quit-requested', self.__sm_quit_requested_cb)
+ self._xsmp_client.connect('quit', self.__sm_quit_cb)
+ self._xsmp_client.startup()
+
accel_group = gtk.AccelGroup()
self.set_data('sugar-accel-group', accel_group)
self.add_accel_group(accel_group)
@@ -556,6 +562,13 @@ class Activity(Window, gtk.Container):
Window.set_canvas(self, canvas)
canvas.connect('map', self.__canvas_map_cb)
+ def __sm_quit_requested_cb(self, client):
+ client.will_quit(True)
+
+ def __sm_quit_cb(self, client):
+ print 'sm quit'
+ self.close(force=True)
+
def __canvas_map_cb(self, canvas):
if self._jobject and self._jobject.file_path:
self.read_file(self._jobject.file_path)
diff --git a/src/sugar/app.c b/src/sugar/app.c
new file mode 100644
index 0000000..785e52d
--- /dev/null
+++ b/src/sugar/app.c
@@ -0,0 +1,396 @@
+/* app.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <glib.h>
+#include <string.h>
+#include <sys/wait.h>
+
+#include "app.h"
+
+enum {
+ EXITED,
+ REGISTERED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum {
+ PROP_0,
+
+ PROP_DESKTOP_FILE,
+ PROP_CLIENT_ID,
+
+ LAST_PROP
+};
+
+static void set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec);
+static void dispose (GObject *object);
+
+static const char *get_basename (GsmApp *app);
+static pid_t launch (GsmApp *app, GError **err);
+
+G_DEFINE_TYPE (GsmApp, gsm_app, G_TYPE_OBJECT)
+
+static void
+gsm_app_init (GsmApp *app)
+{
+ app->pid = -1;
+}
+
+static void
+gsm_app_class_init (GsmAppClass *app_class)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (app_class);
+
+ object_class->set_property = set_property;
+ object_class->get_property = get_property;
+ object_class->dispose = dispose;
+
+ app_class->get_basename = get_basename;
+ app_class->launch = launch;
+
+ g_object_class_install_property (object_class,
+ PROP_DESKTOP_FILE,
+ g_param_spec_string ("desktop-file",
+ "Desktop file",
+ "Freedesktop .desktop file",
+ NULL,
+ G_PARAM_READWRITE));
+ g_object_class_install_property (object_class,
+ PROP_CLIENT_ID,
+ g_param_spec_string ("client-id",
+ "Client ID",
+ "Session management client ID",
+ NULL,
+ G_PARAM_READWRITE));
+
+ signals[EXITED] =
+ g_signal_new ("exited",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmAppClass, exited),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[REGISTERED] =
+ g_signal_new ("registered",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmAppClass, registered),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+ const GValue *value, GParamSpec *pspec)
+{
+ GsmApp *app = GSM_APP (object);
+ const char *desktop_file;
+ char *phase;
+ GError *error = NULL;
+
+ switch (prop_id)
+ {
+ case PROP_DESKTOP_FILE:
+ if (app->desktop_file)
+ egg_desktop_file_free (app->desktop_file);
+ desktop_file = g_value_get_string (value);
+ if (!desktop_file)
+ {
+ app->desktop_file = NULL;
+ break;
+ }
+
+ app->desktop_file = egg_desktop_file_new (desktop_file, &error);
+ if (!app->desktop_file)
+ {
+ g_warning ("Could not parse desktop file %s: %s",
+ desktop_file, error->message);
+ g_error_free (error);
+ break;
+ }
+
+ phase = egg_desktop_file_get_string (app->desktop_file,
+ "X-GNOME-Autostart-Phase", NULL);
+ if (phase)
+ {
+ if (!strcmp (phase, "Initialization"))
+ app->phase = GSM_SESSION_PHASE_INITIALIZATION;
+ else if (!strcmp (phase, "WindowManager"))
+ app->phase = GSM_SESSION_PHASE_WINDOW_MANAGER;
+ else if (!strcmp (phase, "Panel"))
+ app->phase = GSM_SESSION_PHASE_PANEL;
+ else if (!strcmp (phase, "Desktop"))
+ app->phase = GSM_SESSION_PHASE_DESKTOP;
+ else
+ app->phase = GSM_SESSION_PHASE_APPLICATION;
+
+ g_free (phase);
+ }
+ else
+ app->phase = GSM_SESSION_PHASE_APPLICATION;
+ break;
+
+ case PROP_CLIENT_ID:
+ g_free (app->client_id);
+ app->client_id = g_value_dup_string (value);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+ GValue *value, GParamSpec *pspec)
+{
+ GsmApp *app = GSM_APP (object);
+
+ switch (prop_id)
+ {
+ case PROP_DESKTOP_FILE:
+ if (app->desktop_file)
+ g_value_set_string (value, egg_desktop_file_get_source (app->desktop_file));
+ else
+ g_value_set_string (value, NULL);
+ break;
+
+ case PROP_CLIENT_ID:
+ g_value_set_string (value, app->client_id);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+dispose(GObject *object)
+{
+ GsmApp *app = GSM_APP (object);
+
+ if (app->desktop_file)
+ {
+ egg_desktop_file_free (app->desktop_file);
+ app->desktop_file = NULL;
+ }
+
+ if (app->startup_id)
+ {
+ g_free (app->startup_id);
+ app->startup_id = NULL;
+ }
+
+ if (app->client_id)
+ {
+ g_free (app->client_id);
+ app->client_id = NULL;
+ }
+}
+
+/**
+ * gsm_app_get_basename:
+ * @app: a %GsmApp
+ *
+ * Returns an identifying name for @app, e.g. the basename of the path to
+ * @app's desktop file (if any).
+ *
+ * Return value: an identifying name for @app, or %NULL.
+ **/
+const char *
+gsm_app_get_basename (GsmApp *app)
+{
+ return GSM_APP_GET_CLASS (app)->get_basename (app);
+}
+
+static const char *
+get_basename (GsmApp *app)
+{
+ const char *location, *slash;
+
+ if (!app->desktop_file)
+ return NULL;
+
+ location = egg_desktop_file_get_source (app->desktop_file);
+
+ slash = strrchr (location, '/');
+ if (slash)
+ return slash + 1;
+ else
+ return location;
+}
+
+/**
+ * gsm_app_get_phase:
+ * @app: a %GsmApp
+ *
+ * Returns @app's startup phase.
+ *
+ * Return value: @app's startup phase
+ **/
+GsmSessionPhase
+gsm_app_get_phase (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), GSM_SESSION_PHASE_APPLICATION);
+
+ return app->phase;
+}
+
+/**
+ * gsm_app_is_disabled:
+ * @app: a %GsmApp
+ *
+ * Tests if @app is disabled
+ *
+ * Return value: whether or not @app is disabled
+ **/
+gboolean
+gsm_app_is_disabled (GsmApp *app)
+{
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (GSM_APP_GET_CLASS (app)->is_disabled)
+ return GSM_APP_GET_CLASS (app)->is_disabled (app);
+ else
+ return FALSE;
+}
+
+gboolean
+gsm_app_provides (GsmApp *app, const char *service)
+{
+ char **provides;
+ gsize len, i;
+
+ g_return_val_if_fail (GSM_IS_APP (app), FALSE);
+
+ if (!app->desktop_file)
+ return FALSE;
+
+ provides = egg_desktop_file_get_string_list (app->desktop_file,
+ "X-GNOME-Provides",
+ &len, NULL);
+ if (!provides)
+ return FALSE;
+
+ for (i = 0; i < len; i++)
+ {
+ if (!strcmp (provides[i], service))
+ {
+ g_strfreev (provides);
+ return TRUE;
+ }
+ }
+
+ g_strfreev (provides);
+ return FALSE;
+}
+
+static void
+app_exited (GPid pid, gint status, gpointer data)
+{
+ if (WIFEXITED (status))
+ g_signal_emit (GSM_APP (data), signals[EXITED], 0);
+}
+
+static pid_t
+launch (GsmApp *app,
+ GError **err)
+{
+ char *env[2] = { NULL, NULL };
+ gboolean success;
+
+ g_return_val_if_fail (app->desktop_file != NULL, (pid_t)-1);
+
+ if (egg_desktop_file_get_boolean (app->desktop_file,
+ "X-GNOME-Autostart-Notify", NULL) ||
+ egg_desktop_file_get_boolean (app->desktop_file,
+ "AutostartNotify", NULL))
+ env[0] = g_strdup_printf ("DESKTOP_AUTOSTART_ID=%s", app->client_id);
+
+#if 0
+ g_debug ("launching %s with client_id %s\n",
+ gsm_app_get_basename (app), app->client_id);
+#endif
+
+ success =
+ egg_desktop_file_launch (app->desktop_file, NULL, err,
+ EGG_DESKTOP_FILE_LAUNCH_PUTENV, env,
+ EGG_DESKTOP_FILE_LAUNCH_FLAGS, G_SPAWN_DO_NOT_REAP_CHILD,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, &app->pid,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID, &app->startup_id,
+ NULL);
+
+ g_free (env[0]);
+
+ if (success)
+ {
+ /* In case the app belongs to Initialization phase, we monitor
+ * if it exits to emit proper "exited" signal to session. */
+ if (app->phase == GSM_SESSION_PHASE_INITIALIZATION)
+ g_child_watch_add ((GPid) app->pid, app_exited, app);
+
+ return app->pid;
+ }
+ else
+ return (pid_t) -1;
+}
+
+/**
+ * gsm_app_launch:
+ * @app: a %GsmApp
+ * @err: an error pointer
+ *
+ * Launches @app
+ *
+ * Return value: the pid of the new process, or -1 on error
+ **/
+pid_t
+gsm_app_launch (GsmApp *app, GError **err)
+{
+ return GSM_APP_GET_CLASS (app)->launch (app, err);
+}
+
+/**
+ * gsm_app_registered:
+ * @app: a %GsmApp
+ *
+ * Emits "registered" signal in @app
+ **/
+void
+gsm_app_registered (GsmApp *app)
+{
+ g_return_if_fail (GSM_IS_APP (app));
+
+ g_signal_emit (app, signals[REGISTERED], 0);
+}
+
diff --git a/src/sugar/app.h b/src/sugar/app.h
new file mode 100644
index 0000000..be3672c
--- /dev/null
+++ b/src/sugar/app.h
@@ -0,0 +1,70 @@
+/* gsmapp.h
+ * Copyright (C) 2006 Novell, Inc.
+ *
+ */
+
+#ifndef __GSM_APP_H__
+#define __GSM_APP_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+#include "eggdesktopfile.h"
+#include "session.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_APP (gsm_app_get_type ())
+#define GSM_APP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_APP, GsmApp))
+#define GSM_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_APP, GsmAppClass))
+#define GSM_IS_APP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_APP))
+#define GSM_IS_APP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_APP))
+#define GSM_APP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_APP, GsmAppClass))
+
+typedef struct _GsmApp GsmApp;
+typedef struct _GsmAppClass GsmAppClass;
+typedef struct _GsmAppPrivate GsmAppPrivate;
+
+struct _GsmApp
+{
+ GObject parent;
+
+ EggDesktopFile *desktop_file;
+ GsmSessionPhase phase;
+
+ pid_t pid;
+ char *startup_id, *client_id;
+};
+
+struct _GsmAppClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*exited) (GsmApp *app, int status);
+ void (*registered) (GsmApp *app);
+
+ /* virtual methods */
+ const char *(*get_basename) (GsmApp *app);
+ gboolean (*is_disabled) (GsmApp *app);
+ pid_t (*launch) (GsmApp *app, GError **err);
+ void (*set_client) (GsmApp *app, GsmClient *client);
+};
+
+GType gsm_app_get_type (void) G_GNUC_CONST;
+
+const char *gsm_app_get_basename (GsmApp *app);
+GsmSessionPhase gsm_app_get_phase (GsmApp *app);
+gboolean gsm_app_provides (GsmApp *app,
+ const char *service);
+gboolean gsm_app_is_disabled (GsmApp *app);
+pid_t gsm_app_launch (GsmApp *app,
+ GError **err);
+void gsm_app_set_client (GsmApp *app,
+ GsmClient *client);
+
+void gsm_app_registered (GsmApp *app);
+
+G_END_DECLS
+
+#endif /* __GSM_APP_H__ */
diff --git a/src/sugar/client-xsmp.c b/src/sugar/client-xsmp.c
new file mode 100644
index 0000000..d422683
--- /dev/null
+++ b/src/sugar/client-xsmp.c
@@ -0,0 +1,828 @@
+/* client-xsmp.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "client-xsmp.h"
+#include "session.h"
+
+/* FIXME */
+#define GsmDesktopFile "_Gsm_DesktopFile"
+
+static gboolean client_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer data);
+static gboolean client_protocol_timeout (gpointer data);
+
+static void set_description (GsmClientXSMP *xsmp);
+
+static const char *xsmp_get_client_id (GsmClient *client);
+static pid_t xsmp_get_pid (GsmClient *client);
+static char *xsmp_get_desktop_file (GsmClient *client);
+static char *xsmp_get_restart_command (GsmClient *client);
+static char *xsmp_get_discard_command (GsmClient *client);
+static gboolean xsmp_get_autorestart (GsmClient *client);
+
+static void xsmp_finalize (GObject *object);
+static void xsmp_restart (GsmClient *client,
+ GError **error);
+static void xsmp_save_yourself (GsmClient *client,
+ gboolean save_state);
+static void xsmp_save_yourself_phase2 (GsmClient *client);
+static void xsmp_interact (GsmClient *client);
+static void xsmp_shutdown_cancelled (GsmClient *client);
+static void xsmp_die (GsmClient *client);
+
+G_DEFINE_TYPE (GsmClientXSMP, gsm_client_xsmp, GSM_TYPE_CLIENT)
+
+static void
+gsm_client_xsmp_init (GsmClientXSMP *xsmp)
+{
+ ;
+}
+
+static void
+gsm_client_xsmp_class_init (GsmClientXSMPClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GsmClientClass *client_class = GSM_CLIENT_CLASS (klass);
+
+ object_class->finalize = xsmp_finalize;
+
+ client_class->get_client_id = xsmp_get_client_id;
+ client_class->get_pid = xsmp_get_pid;
+ client_class->get_desktop_file = xsmp_get_desktop_file;
+ client_class->get_restart_command = xsmp_get_restart_command;
+ client_class->get_discard_command = xsmp_get_discard_command;
+ client_class->get_autorestart = xsmp_get_autorestart;
+
+ client_class->restart = xsmp_restart;
+ client_class->save_yourself = xsmp_save_yourself;
+ client_class->save_yourself_phase2 = xsmp_save_yourself_phase2;
+ client_class->interact = xsmp_interact;
+ client_class->shutdown_cancelled = xsmp_shutdown_cancelled;
+ client_class->die = xsmp_die;
+}
+
+GsmClientXSMP *
+gsm_client_xsmp_new (IceConn ice_conn)
+{
+ GsmClientXSMP *xsmp;
+ GIOChannel *channel;
+ int fd;
+
+ xsmp = g_object_new (GSM_TYPE_CLIENT_XSMP, NULL);
+ xsmp->props = g_ptr_array_new ();
+
+ xsmp->ice_conn = ice_conn;
+ xsmp->current_save_yourself = -1;
+ xsmp->next_save_yourself = -1;
+
+ fd = IceConnectionNumber (ice_conn);
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+
+ channel = g_io_channel_unix_new (fd);
+ xsmp->watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
+ client_iochannel_watch, xsmp);
+ g_io_channel_unref (channel);
+
+ xsmp->protocol_timeout = g_timeout_add (5000, client_protocol_timeout, xsmp);
+
+ set_description (xsmp);
+ g_debug ("New client '%s'", xsmp->description);
+
+ return xsmp;
+}
+
+static void
+xsmp_finalize (GObject *object)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) object;
+
+ g_debug ("xsmp_finalize (%s)", xsmp->description);
+
+ if (xsmp->watch_id)
+ g_source_remove (xsmp->watch_id);
+
+ if (xsmp->conn)
+ SmsCleanUp (xsmp->conn);
+ else
+ IceCloseConnection (xsmp->ice_conn);
+
+ if (xsmp->protocol_timeout)
+ g_source_remove (xsmp->protocol_timeout);
+
+ G_OBJECT_CLASS (gsm_client_xsmp_parent_class)->finalize (object);
+}
+
+static gboolean
+client_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer data)
+{
+ GsmClient *client = data;
+ GsmClientXSMP *xsmp = data;
+
+ switch (IceProcessMessages (xsmp->ice_conn, NULL, NULL))
+ {
+ case IceProcessMessagesSuccess:
+ return TRUE;
+
+ case IceProcessMessagesIOError:
+ g_debug ("IceProcessMessagesIOError on '%s'", xsmp->description);
+ gsm_client_disconnected (client);
+ return FALSE;
+
+ case IceProcessMessagesConnectionClosed:
+ g_debug ("IceProcessMessagesConnectionClosed on '%s'",
+ xsmp->description);
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/* Called if too much time passes between the initial connection and
+ * the XSMP protocol setup.
+ */
+static gboolean
+client_protocol_timeout (gpointer data)
+{
+ GsmClient *client = data;
+ GsmClientXSMP *xsmp = data;
+
+ g_debug ("client_protocol_timeout for client '%s' in ICE status %d",
+ xsmp->description, IceConnectionStatus (xsmp->ice_conn));
+ gsm_client_disconnected (client);
+
+ return FALSE;
+}
+
+static Status
+register_client_callback (SmsConn conn,
+ SmPointer manager_data,
+ char *previous_id)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+ char *id;
+
+ g_debug ("Client '%s' received RegisterClient(%s)",
+ xsmp->description,
+ previous_id ? previous_id : "NULL");
+
+ id = gsm_session_register_client (global_session, client, previous_id);
+
+ if (id == NULL)
+ {
+ g_debug (" rejected: invalid previous_id");
+ free (previous_id);
+ return FALSE;
+ }
+
+ xsmp->id = id;
+
+ set_description (xsmp);
+
+ g_debug ("Sending RegisterClientReply to '%s'", xsmp->description);
+
+ SmsRegisterClientReply (conn, xsmp->id);
+
+ if (!previous_id)
+ {
+ /* Send the initial SaveYourself. */
+ g_debug ("Sending initial SaveYourself");
+ SmsSaveYourself (conn, SmSaveLocal, False, SmInteractStyleNone, False);
+ xsmp->current_save_yourself = SmSaveLocal;
+
+ free (previous_id);
+ }
+
+ return TRUE;
+}
+
+static void
+do_save_yourself (GsmClientXSMP *xsmp, int save_type)
+{
+ if (xsmp->next_save_yourself != -1)
+ {
+ /* Either we're currently doing a shutdown and there's a checkpoint
+ * queued after it, or vice versa. Either way, the new SaveYourself
+ * is redundant.
+ */
+ g_debug (" skipping redundant SaveYourself for '%s'",
+ xsmp->description);
+ }
+ else if (xsmp->current_save_yourself != -1)
+ {
+ g_debug (" queuing new SaveYourself for '%s'",
+ xsmp->description);
+ xsmp->next_save_yourself = save_type;
+ }
+ else
+ {
+ xsmp->current_save_yourself = save_type;
+
+ switch (save_type)
+ {
+ case SmSaveLocal:
+ /* Save state */
+ SmsSaveYourself (xsmp->conn, SmSaveLocal, FALSE,
+ SmInteractStyleNone, FALSE);
+ break;
+
+ default:
+ /* Logout */
+ SmsSaveYourself (xsmp->conn, save_type, TRUE,
+ SmInteractStyleAny, FALSE);
+ break;
+ }
+ }
+}
+
+static void
+save_yourself_request_callback (SmsConn conn,
+ SmPointer manager_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast,
+ Bool global)
+{
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received SaveYourselfRequest(%s, %s, %s, %s, %s)",
+ xsmp->description,
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ global ? "Global" : "!Global");
+
+ /* Examining the g_debug above, you can see that there are a total
+ * of 72 different combinations of options that this could have been
+ * called with. However, most of them are stupid.
+ *
+ * If @shutdown and @global are both TRUE, that means the caller is
+ * requesting that a logout message be sent to all clients, so we do
+ * that. We use @fast to decide whether or not to show a
+ * confirmation dialog. (This isn't really what @fast is for, but
+ * the old gnome-session and ksmserver both interpret it that way,
+ * so we do too.) We ignore @save_type because we pick the correct
+ * save_type ourselves later based on user prefs, dialog choices,
+ * etc, and we ignore @interact_style, because clients have not used
+ * it correctly consistently enough to make it worth honoring.
+ *
+ * If @shutdown is TRUE and @global is FALSE, the caller is
+ * confused, so we ignore the request.
+ *
+ * If @shutdown is FALSE and @save_type is SmSaveGlobal or
+ * SmSaveBoth, then the client wants us to ask some or all open
+ * applications to save open files to disk, but NOT quit. This is
+ * silly and so we ignore the request.
+ *
+ * If @shutdown is FALSE and @save_type is SmSaveLocal, then the
+ * client wants us to ask some or all open applications to update
+ * their current saved state, but not log out. At the moment, the
+ * code only supports this for the !global case (ie, a client
+ * requesting that it be allowed to update *its own* saved state,
+ * but not having everyone else update their saved state).
+ */
+
+ if (shutdown && global)
+ {
+ g_debug (" initiating shutdown");
+/* gsm_session_initiate_shutdown (global_session,
+ !fast,
+ GSM_SESSION_LOGOUT_TYPE_LOGOUT);
+*/
+ }
+ else if (!shutdown && !global)
+ {
+ g_debug (" initiating checkpoint");
+ do_save_yourself (xsmp, SmSaveLocal);
+ }
+ else
+ g_debug (" ignoring");
+}
+
+static void
+xsmp_restart (GsmClient *client, GError **error)
+{
+ char *restart_cmd = gsm_client_get_restart_command (client);
+
+ g_spawn_command_line_async (restart_cmd, error);
+
+ g_free (restart_cmd);
+}
+
+static void
+xsmp_save_yourself (GsmClient *client, gboolean save_state)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *)client;
+
+ g_debug ("xsmp_save_yourself ('%s', %s)", xsmp->description,
+ save_state ? "True" : "False");
+
+ do_save_yourself (xsmp, save_state ? SmSaveBoth : SmSaveGlobal);
+}
+
+static void
+save_yourself_phase2_request_callback (SmsConn conn,
+ SmPointer manager_data)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received SaveYourselfPhase2Request",
+ xsmp->description);
+
+ if (xsmp->current_save_yourself == SmSaveLocal)
+ {
+ /* WTF? Anyway, if it's checkpointing, it doesn't have to wait
+ * for anyone else.
+ */
+ SmsSaveYourselfPhase2 (xsmp->conn);
+ }
+ else
+ gsm_client_request_phase2 (client);
+}
+
+static void
+xsmp_save_yourself_phase2 (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *)client;
+
+ g_debug ("xsmp_save_yourself_phase2 ('%s')", xsmp->description);
+
+ SmsSaveYourselfPhase2 (xsmp->conn);
+}
+
+static void
+interact_request_callback (SmsConn conn,
+ SmPointer manager_data,
+ int dialog_type)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received InteractRequest(%s)", xsmp->description,
+ dialog_type == SmInteractStyleAny ? "Any" : "Errors");
+
+ gsm_client_request_interaction (client);
+}
+
+static void
+xsmp_interact (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ g_debug ("xsmp_interact ('%s')", xsmp->description);
+
+ SmsInteract (xsmp->conn);
+}
+
+static void
+interact_done_callback (SmsConn conn,
+ SmPointer manager_data,
+ Bool cancel_shutdown)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received InteractDone(cancel_shutdown = %s)",
+ xsmp->description, cancel_shutdown ? "True" : "False");
+
+ gsm_client_interaction_done (client, cancel_shutdown);
+}
+
+static void
+xsmp_shutdown_cancelled (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ g_debug ("xsmp_shutdown_cancelled ('%s')", xsmp->description);
+
+ SmsShutdownCancelled (xsmp->conn);
+}
+
+static void
+xsmp_die (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ g_debug ("xsmp_die ('%s')", xsmp->description);
+
+ SmsDie (xsmp->conn);
+}
+
+static void
+save_yourself_done_callback (SmsConn conn,
+ SmPointer manager_data,
+ Bool success)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+
+ g_debug ("Client '%s' received SaveYourselfDone(success = %s)",
+ xsmp->description, success ? "True" : "False");
+
+ if (xsmp->current_save_yourself == SmSaveLocal)
+ {
+ xsmp->current_save_yourself = -1;
+ SmsSaveComplete (xsmp->conn);
+ gsm_client_saved_state (client);
+ }
+ else
+ {
+ xsmp->current_save_yourself = -1;
+ gsm_client_save_yourself_done (client);
+ }
+
+ if (xsmp->next_save_yourself)
+ {
+ int save_type = xsmp->next_save_yourself;
+
+ xsmp->next_save_yourself = -1;
+ do_save_yourself (xsmp, save_type);
+ }
+}
+
+static void
+close_connection_callback (SmsConn conn,
+ SmPointer manager_data,
+ int count,
+ char **reason_msgs)
+{
+ GsmClient *client = manager_data;
+ GsmClientXSMP *xsmp = manager_data;
+ int i;
+
+ g_debug ("Client '%s' received CloseConnection", xsmp->description);
+ for (i = 0; i < count; i++)
+ g_debug (" close reason: '%s'", reason_msgs[i]);
+ SmFreeReasons (count, reason_msgs);
+
+ gsm_client_disconnected (client);
+}
+
+static void
+debug_print_property (SmProp *prop)
+{
+ GString *tmp;
+ int i;
+
+ switch (prop->type[0])
+ {
+ case 'C': /* CARD8 */
+ g_debug (" %s = %d", prop->name, *(unsigned char *)prop->vals[0].value);
+ break;
+
+ case 'A': /* ARRAY8 */
+ g_debug (" %s = '%s'", prop->name, (char *)prop->vals[0].value);
+ break;
+
+ case 'L': /* LISTofARRAY8 */
+ tmp = g_string_new (NULL);
+ for (i = 0; i < prop->num_vals; i++)
+ {
+ g_string_append_printf (tmp, "'%.*s' ", prop->vals[i].length,
+ (char *)prop->vals[i].value);
+ }
+ g_debug (" %s = %s", prop->name, tmp->str);
+ g_string_free (tmp, TRUE);
+ break;
+
+ default:
+ g_debug (" %s = ??? (%s)", prop->name, prop->type);
+ break;
+ }
+}
+
+static SmProp *
+find_property (GsmClientXSMP *client, const char *name, int *index)
+{
+ SmProp *prop;
+ int i;
+
+ for (i = 0; i < client->props->len; i++)
+ {
+ prop = client->props->pdata[i];
+
+ if (!strcmp (prop->name, name))
+ {
+ if (index)
+ *index = i;
+ return prop;
+ }
+ }
+
+ return NULL;
+}
+
+static void
+delete_property (GsmClientXSMP *client, const char *name)
+{
+ int index;
+ SmProp *prop;
+
+ prop = find_property (client, name, &index);
+ if (!prop)
+ return;
+
+#if 0
+ /* This is wrong anyway; we can't unconditionally run the current
+ * discard command; if this client corresponds to a GsmAppResumed,
+ * and the current discard command is identical to the app's
+ * discard_command, then we don't run the discard command now,
+ * because that would delete a saved state we may want to resume
+ * again later.
+ */
+ if (!strcmp (name, SmDiscardCommand))
+ gsm_client_run_discard (client);
+#endif
+
+ g_ptr_array_remove_index_fast (client->props, index);
+ SmFreeProperty (prop);
+}
+
+static void
+set_properties_callback (SmsConn conn,
+ SmPointer manager_data,
+ int num_props,
+ SmProp **props)
+{
+ GsmClientXSMP *client = manager_data;
+ int i;
+
+ g_debug ("Set properties from client '%s'", client->description);
+
+ for (i = 0; i < num_props; i++)
+ {
+ delete_property (client, props[i]->name);
+ g_ptr_array_add (client->props, props[i]);
+
+ debug_print_property (props[i]);
+
+ if (!strcmp (props[i]->name, SmProgram))
+ set_description (client);
+ }
+
+ free (props);
+
+}
+
+static void
+delete_properties_callback (SmsConn conn,
+ SmPointer manager_data,
+ int num_props,
+ char **prop_names)
+{
+ GsmClientXSMP *client = manager_data;
+ int i;
+
+ g_debug ("Delete properties from '%s'", client->description);
+
+ for (i = 0; i < num_props; i++)
+ {
+ delete_property (client, prop_names[i]);
+
+ g_debug (" %s", prop_names[i]);
+ }
+
+ free (prop_names);
+}
+
+static void
+get_properties_callback (SmsConn conn,
+ SmPointer manager_data)
+{
+ GsmClientXSMP *client = manager_data;
+
+ g_debug ("Get properties request from '%s'", client->description);
+
+ SmsReturnProperties (conn, client->props->len,
+ (SmProp **)client->props->pdata);
+}
+
+static const char *
+xsmp_get_client_id (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+
+ return xsmp->id;
+}
+
+static pid_t
+xsmp_get_pid (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmProcessID, NULL);
+ char buf[32];
+
+ if (!prop || strcmp (prop->type, SmARRAY8) != 0)
+ return (pid_t)-1;
+
+ /* prop->vals[0].value might not be '\0'-terminated... */
+ g_strlcpy (buf, prop->vals[0].value, MIN (prop->vals[0].length, sizeof (buf)));
+ return (pid_t)strtoul (buf, NULL, 10);
+}
+
+static char *
+xsmp_get_desktop_file (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, GsmDesktopFile, NULL);
+
+ if (!prop || strcmp (prop->type, SmARRAY8) != 0)
+ return NULL;
+
+ return g_strndup (prop->vals[0].value, prop->vals[0].length);
+}
+
+static char *
+prop_to_command (SmProp *prop)
+{
+ GString *str;
+ int i, j;
+ gboolean need_quotes;
+
+ str = g_string_new (NULL);
+ for (i = 0; i < prop->num_vals; i++)
+ {
+ char *val = prop->vals[i].value;
+
+ need_quotes = FALSE;
+ for (j = 0; j < prop->vals[i].length; j++)
+ {
+ if (!g_ascii_isalnum (val[j]) && !strchr ("-_=:./", val[j]))
+ {
+ need_quotes = TRUE;
+ break;
+ }
+ }
+
+ if (i > 0)
+ g_string_append_c (str, ' ');
+
+ if (!need_quotes)
+ {
+ g_string_append_printf (str, "%.*s", prop->vals[i].length,
+ (char *)prop->vals[i].value);
+ }
+ else
+ {
+ g_string_append_c (str, '\'');
+ while (val < (char *)prop->vals[i].value + prop->vals[i].length)
+ {
+ if (*val == '\'')
+ g_string_append (str, "'\''");
+ else
+ g_string_append_c (str, *val);
+ val++;
+ }
+ g_string_append_c (str, '\'');
+ }
+ }
+
+ return g_string_free (str, FALSE);
+}
+
+static char *
+xsmp_get_restart_command (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmRestartCommand, NULL);
+
+ if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0)
+ return NULL;
+
+ return prop_to_command (prop);
+}
+
+static char *
+xsmp_get_discard_command (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmDiscardCommand, NULL);
+
+ if (!prop || strcmp (prop->type, SmLISTofARRAY8) != 0)
+ return NULL;
+
+ return prop_to_command (prop);
+}
+
+static gboolean
+xsmp_get_autorestart (GsmClient *client)
+{
+ GsmClientXSMP *xsmp = (GsmClientXSMP *) client;
+ SmProp *prop = find_property (xsmp, SmRestartStyleHint, NULL);
+
+ if (!prop || strcmp (prop->type, SmCARD8) != 0)
+ return FALSE;
+
+ return ((unsigned char *)prop->vals[0].value)[0] == SmRestartImmediately;
+}
+
+static void
+set_description (GsmClientXSMP *client)
+{
+ SmProp *prop = find_property (client, SmProgram, NULL);
+
+ g_free (client->description);
+ if (prop)
+ {
+ client->description = g_strdup_printf ("%p [%.*s %s]", client,
+ prop->vals[0].length,
+ (char *)prop->vals[0].value,
+ client->id);
+ }
+ else if (client->id)
+ client->description = g_strdup_printf ("%p [%s]", client, client->id);
+ else
+ client->description = g_strdup_printf ("%p", client);
+}
+
+void
+gsm_client_xsmp_connect (GsmClientXSMP *client, SmsConn conn,
+ unsigned long *mask_ret, SmsCallbacks *callbacks_ret)
+{
+ client->conn = conn;
+
+ if (client->protocol_timeout)
+ {
+ g_source_remove (client->protocol_timeout);
+ client->protocol_timeout = 0;
+ }
+
+ g_debug ("Initializing client %s", client->description);
+
+ *mask_ret = 0;
+
+ *mask_ret |= SmsRegisterClientProcMask;
+ callbacks_ret->register_client.callback = register_client_callback;
+ callbacks_ret->register_client.manager_data = client;
+
+ *mask_ret |= SmsInteractRequestProcMask;
+ callbacks_ret->interact_request.callback = interact_request_callback;
+ callbacks_ret->interact_request.manager_data = client;
+
+ *mask_ret |= SmsInteractDoneProcMask;
+ callbacks_ret->interact_done.callback = interact_done_callback;
+ callbacks_ret->interact_done.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfRequestProcMask;
+ callbacks_ret->save_yourself_request.callback = save_yourself_request_callback;
+ callbacks_ret->save_yourself_request.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfP2RequestProcMask;
+ callbacks_ret->save_yourself_phase2_request.callback = save_yourself_phase2_request_callback;
+ callbacks_ret->save_yourself_phase2_request.manager_data = client;
+
+ *mask_ret |= SmsSaveYourselfDoneProcMask;
+ callbacks_ret->save_yourself_done.callback = save_yourself_done_callback;
+ callbacks_ret->save_yourself_done.manager_data = client;
+
+ *mask_ret |= SmsCloseConnectionProcMask;
+ callbacks_ret->close_connection.callback = close_connection_callback;
+ callbacks_ret->close_connection.manager_data = client;
+
+ *mask_ret |= SmsSetPropertiesProcMask;
+ callbacks_ret->set_properties.callback = set_properties_callback;
+ callbacks_ret->set_properties.manager_data = client;
+
+ *mask_ret |= SmsDeletePropertiesProcMask;
+ callbacks_ret->delete_properties.callback = delete_properties_callback;
+ callbacks_ret->delete_properties.manager_data = client;
+
+ *mask_ret |= SmsGetPropertiesProcMask;
+ callbacks_ret->get_properties.callback = get_properties_callback;
+ callbacks_ret->get_properties.manager_data = client;
+}
diff --git a/src/sugar/client-xsmp.h b/src/sugar/client-xsmp.h
new file mode 100644
index 0000000..c744ee1
--- /dev/null
+++ b/src/sugar/client-xsmp.h
@@ -0,0 +1,70 @@
+/* client-xsmp.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifndef __GSM_CLIENT_XSMP_H__
+#define __GSM_CLIENT_XSMP_H__
+
+#include "client.h"
+
+#include <X11/SM/SMlib.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_CLIENT_XSMP (gsm_client_xsmp_get_type ())
+#define GSM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CLIENT_XSMP, GsmClientXSMP))
+#define GSM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CLIENT_XSMP, GsmClientXSMPClass))
+#define GSM_IS_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CLIENT_XSMP))
+#define GSM_IS_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CLIENT_XSMP))
+#define GSM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_CLIENT_XSMP, GsmClientXSMPClass))
+
+typedef struct _GsmClientXSMP GsmClientXSMP;
+typedef struct _GsmClientXSMPClass GsmClientXSMPClass;
+
+struct _GsmClientXSMP
+{
+ GsmClient parent;
+
+ SmsConn conn;
+ IceConn ice_conn;
+
+ guint watch_id, protocol_timeout;
+
+ int current_save_yourself, next_save_yourself;
+ char *id, *description;
+ GPtrArray *props;
+};
+
+struct _GsmClientXSMPClass
+{
+ GsmClientClass parent_class;
+
+};
+
+GType gsm_client_xsmp_get_type (void) G_GNUC_CONST;
+
+GsmClientXSMP *gsm_client_xsmp_new (IceConn ice_conn);
+
+void gsm_client_xsmp_connect (GsmClientXSMP *client,
+ SmsConn conn,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret);
+
+G_END_DECLS
+
+#endif /* __GSM_CLIENT_XSMP_H__ */
diff --git a/src/sugar/client.c b/src/sugar/client.c
new file mode 100644
index 0000000..4865523
--- /dev/null
+++ b/src/sugar/client.c
@@ -0,0 +1,251 @@
+/* client.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "client.h"
+
+enum {
+ SAVED_STATE,
+ REQUEST_PHASE2,
+ REQUEST_INTERACTION,
+ INTERACTION_DONE,
+ SAVE_YOURSELF_DONE,
+ DISCONNECTED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsmClient, gsm_client, G_TYPE_OBJECT)
+
+static void
+gsm_client_init (GsmClient *client)
+{
+ ;
+}
+
+static void
+gsm_client_class_init (GsmClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ signals[SAVED_STATE] =
+ g_signal_new ("saved_state",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, saved_state),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[REQUEST_PHASE2] =
+ g_signal_new ("request_phase2",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, request_phase2),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[REQUEST_INTERACTION] =
+ g_signal_new ("request_interaction",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, request_interaction),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[INTERACTION_DONE] =
+ g_signal_new ("interaction_done",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, interaction_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+
+ signals[SAVE_YOURSELF_DONE] =
+ g_signal_new ("save_yourself_done",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, save_yourself_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ signals[DISCONNECTED] =
+ g_signal_new ("disconnected",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmClientClass, disconnected),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+}
+
+const char *
+gsm_client_get_client_id (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_client_id (client);
+}
+
+pid_t
+gsm_client_get_pid (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), -1);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_pid (client);
+}
+
+char *
+gsm_client_get_desktop_file (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_desktop_file (client);
+}
+
+char *
+gsm_client_get_restart_command (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_restart_command (client);
+}
+
+char *
+gsm_client_get_discard_command (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), NULL);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_discard_command (client);
+}
+
+gboolean
+gsm_client_get_autorestart (GsmClient *client)
+{
+ g_return_val_if_fail (GSM_IS_CLIENT (client), FALSE);
+
+ return GSM_CLIENT_GET_CLASS (client)->get_autorestart (client);
+}
+
+void
+gsm_client_save_state (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+}
+
+void
+gsm_client_restart (GsmClient *client, GError **error)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->restart (client, error);
+}
+
+void
+gsm_client_save_yourself (GsmClient *client,
+ gboolean save_state)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->save_yourself (client, save_state);
+}
+
+void
+gsm_client_save_yourself_phase2 (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->save_yourself_phase2 (client);
+}
+
+void
+gsm_client_interact (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->interact (client);
+}
+
+void
+gsm_client_shutdown_cancelled (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->shutdown_cancelled (client);
+}
+
+void
+gsm_client_die (GsmClient *client)
+{
+ g_return_if_fail (GSM_IS_CLIENT (client));
+
+ GSM_CLIENT_GET_CLASS (client)->die (client);
+}
+
+void
+gsm_client_saved_state (GsmClient *client)
+{
+ g_signal_emit (client, signals[SAVED_STATE], 0);
+}
+
+void
+gsm_client_request_phase2 (GsmClient *client)
+{
+ g_signal_emit (client, signals[REQUEST_PHASE2], 0);
+}
+
+void
+gsm_client_request_interaction (GsmClient *client)
+{
+ g_signal_emit (client, signals[REQUEST_INTERACTION], 0);
+}
+
+void
+gsm_client_interaction_done (GsmClient *client, gboolean cancel_shutdown)
+{
+ g_signal_emit (client, signals[INTERACTION_DONE], 0, cancel_shutdown);
+}
+
+void
+gsm_client_save_yourself_done (GsmClient *client)
+{
+ g_signal_emit (client, signals[SAVE_YOURSELF_DONE], 0);
+}
+
+void
+gsm_client_disconnected (GsmClient *client)
+{
+ g_signal_emit (client, signals[DISCONNECTED], 0);
+}
+
diff --git a/src/sugar/client.h b/src/sugar/client.h
new file mode 100644
index 0000000..3e4e751
--- /dev/null
+++ b/src/sugar/client.h
@@ -0,0 +1,111 @@
+/* client.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifndef __GSM_CLIENT_H__
+#define __GSM_CLIENT_H__
+
+#include <glib-object.h>
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_CLIENT (gsm_client_get_type ())
+#define GSM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_CLIENT, GsmClient))
+#define GSM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_CLIENT, GsmClientClass))
+#define GSM_IS_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_CLIENT))
+#define GSM_IS_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_CLIENT))
+#define GSM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_CLIENT, GsmClientClass))
+
+typedef struct _GsmClient GsmClient;
+typedef struct _GsmClientClass GsmClientClass;
+
+struct _GsmClient
+{
+ GObject parent;
+
+};
+
+struct _GsmClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*saved_state) (GsmClient *client);
+
+ void (*request_phase2) (GsmClient *client);
+
+ void (*request_interaction) (GsmClient *client);
+ void (*interaction_done) (GsmClient *client,
+ gboolean cancel_shutdown);
+
+ void (*save_yourself_done) (GsmClient *client);
+
+ void (*disconnected) (GsmClient *client);
+
+ /* virtual methods */
+ const char * (*get_client_id) (GsmClient *client);
+ pid_t (*get_pid) (GsmClient *client);
+ char * (*get_desktop_file) (GsmClient *client);
+ char * (*get_restart_command) (GsmClient *client);
+ char * (*get_discard_command) (GsmClient *client);
+ gboolean (*get_autorestart) (GsmClient *client);
+
+ void (*restart) (GsmClient *client,
+ GError **error);
+ void (*save_yourself) (GsmClient *client,
+ gboolean save_state);
+ void (*save_yourself_phase2) (GsmClient *client);
+ void (*interact) (GsmClient *client);
+ void (*shutdown_cancelled) (GsmClient *client);
+ void (*die) (GsmClient *client);
+};
+
+GType gsm_client_get_type (void) G_GNUC_CONST;
+
+const char *gsm_client_get_client_id (GsmClient *client);
+
+pid_t gsm_client_get_pid (GsmClient *client);
+char *gsm_client_get_desktop_file (GsmClient *client);
+char *gsm_client_get_restart_command (GsmClient *client);
+char *gsm_client_get_discard_command (GsmClient *client);
+gboolean gsm_client_get_autorestart (GsmClient *client);
+
+void gsm_client_save_state (GsmClient *client);
+
+void gsm_client_restart (GsmClient *client,
+ GError **error);
+void gsm_client_save_yourself (GsmClient *client,
+ gboolean save_state);
+void gsm_client_save_yourself_phase2 (GsmClient *client);
+void gsm_client_interact (GsmClient *client);
+void gsm_client_shutdown_cancelled (GsmClient *client);
+void gsm_client_die (GsmClient *client);
+
+/* protected */
+void gsm_client_saved_state (GsmClient *client);
+void gsm_client_request_phase2 (GsmClient *client);
+void gsm_client_request_interaction (GsmClient *client);
+void gsm_client_interaction_done (GsmClient *client,
+ gboolean cancel_shutdown);
+void gsm_client_save_yourself_done (GsmClient *client);
+void gsm_client_disconnected (GsmClient *client);
+
+G_END_DECLS
+
+#endif /* __GSM_CLIENT_H__ */
diff --git a/src/sugar/eggdesktopfile.c b/src/sugar/eggdesktopfile.c
new file mode 100644
index 0000000..ad78345
--- /dev/null
+++ b/src/sugar/eggdesktopfile.c
@@ -0,0 +1,1437 @@
+/* eggdesktopfile.c - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Based on gnome-desktop-item.c
+ * Copyright (C) 1999, 2000 Red Hat Inc.
+ * Copyright (C) 2001 George Lebl
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place -
+ * Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "eggdesktopfile.h"
+
+#include <string.h>
+#include <unistd.h>
+
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <gtk/gtkwindow.h>
+#include <gdk/gdkx.h>
+
+struct EggDesktopFile {
+ GKeyFile *key_file;
+ char *source;
+
+ char *name, *icon;
+ EggDesktopFileType type;
+ char document_code;
+};
+
+/**
+ * egg_desktop_file_new:
+ * @desktop_file_path: path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @desktop_file.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new (const char *desktop_file_path, GError **error)
+{
+ GKeyFile *key_file;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (key_file, desktop_file_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ return egg_desktop_file_new_from_key_file (key_file, desktop_file_path,
+ error);
+}
+
+/**
+ * egg_desktop_file_new_from_data_dirs:
+ * @desktop_file_path: relative path to a Freedesktop-style Desktop file
+ * @error: error pointer
+ *
+ * Looks for @desktop_file_path in the paths returned from
+ * g_get_user_data_dir() and g_get_system_data_dirs(), and creates
+ * a new #EggDesktopFile from it.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ GKeyFile *key_file;
+ char *full_path;
+
+ key_file = g_key_file_new ();
+ if (!g_key_file_load_from_data_dirs (key_file, desktop_file_path,
+ &full_path, 0, error))
+ {
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ desktop_file = egg_desktop_file_new_from_key_file (key_file,
+ full_path,
+ error);
+ g_free (full_path);
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_new_from_key_file:
+ * @key_file: a #GKeyFile representing a desktop file
+ * @source: the path or URI that @key_file was loaded from, or %NULL
+ * @error: error pointer
+ *
+ * Creates a new #EggDesktopFile for @key_file. Assumes ownership of
+ * @key_file (on success or failure); you should consider @key_file to
+ * be freed after calling this function.
+ *
+ * Return value: the new #EggDesktopFile, or %NULL on error.
+ **/
+EggDesktopFile *
+egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error)
+{
+ EggDesktopFile *desktop_file;
+ char *version, *type;
+
+ if (!g_key_file_has_group (key_file, EGG_DESKTOP_FILE_GROUP))
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("File is not a valid .desktop file"));
+ g_key_file_free (key_file);
+ return NULL;
+ }
+
+ version = g_key_file_get_value (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_VERSION,
+ NULL);
+ if (version)
+ {
+ double version_num;
+ char *end;
+
+ version_num = g_ascii_strtod (version, &end);
+ if (*end)
+ {
+ g_warning ("Invalid Version string '%s' in %s",
+ version, source ? source : "(unknown)");
+ }
+ else if (version_num > 1.0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ _("Unrecognized desktop file Version '%s'"), version);
+ g_free (version);
+ g_key_file_free (key_file);
+ return NULL;
+ }
+ else
+ g_free (version);
+ }
+
+ desktop_file = g_new0 (EggDesktopFile, 1);
+ desktop_file->key_file = key_file;
+
+ if (g_path_is_absolute (source))
+ desktop_file->source = g_filename_to_uri (source, NULL, NULL);
+ else
+ desktop_file->source = g_strdup (source);
+
+ desktop_file->name = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME, error);
+ if (!desktop_file->name)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ type = g_key_file_get_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE, error);
+ if (!type)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ if (!strcmp (type, "Application"))
+ {
+ char *exec, *p;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_APPLICATION;
+
+ exec = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+
+ /* See if it takes paths or URIs or neither */
+ for (p = exec; *p; p++)
+ {
+ if (*p == '%')
+ {
+ if (p[1] == '\0' || strchr ("FfUu", p[1]))
+ {
+ desktop_file->document_code = p[1];
+ break;
+ }
+ p++;
+ }
+ }
+
+ g_free (exec);
+ }
+ else if (!strcmp (type, "Link"))
+ {
+ char *url;
+
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_LINK;
+
+ url = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ {
+ egg_desktop_file_free (desktop_file);
+ return NULL;
+ }
+ g_free (url);
+ }
+ else if (!strcmp (type, "Directory"))
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_DIRECTORY;
+ else
+ desktop_file->type = EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED;
+
+ g_free (type);
+
+ /* Check the Icon key */
+ desktop_file->icon = g_key_file_get_string (key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ICON,
+ NULL);
+ if (desktop_file->icon && !g_path_is_absolute (desktop_file->icon))
+ {
+ char *ext;
+
+ /* Lots of .desktop files still get this wrong */
+ ext = strrchr (desktop_file->icon, '.');
+ if (ext && (!strcmp (ext, ".png") ||
+ !strcmp (ext, ".xpm") ||
+ !strcmp (ext, ".svg")))
+ {
+ g_warning ("Desktop file '%s' has malformed Icon key '%s'"
+ "(should not include extension)",
+ source ? source : "(unknown)",
+ desktop_file->icon);
+ *ext = '\0';
+ }
+ }
+
+ return desktop_file;
+}
+
+/**
+ * egg_desktop_file_free:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Frees @desktop_file.
+ **/
+void
+egg_desktop_file_free (EggDesktopFile *desktop_file)
+{
+ g_key_file_free (desktop_file->key_file);
+ g_free (desktop_file->source);
+ g_free (desktop_file->name);
+ g_free (desktop_file->icon);
+ g_free (desktop_file);
+}
+
+/**
+ * egg_desktop_file_get_source:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the URI that @desktop_file was loaded from.
+ *
+ * Return value: @desktop_file's source URI
+ **/
+const char *
+egg_desktop_file_get_source (EggDesktopFile *desktop_file)
+{
+ return desktop_file->source;
+}
+
+/**
+ * egg_desktop_file_get_desktop_file_type:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the desktop file type of @desktop_file.
+ *
+ * Return value: @desktop_file's type
+ **/
+EggDesktopFileType
+egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file)
+{
+ return desktop_file->type;
+}
+
+/**
+ * egg_desktop_file_get_name:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the (localized) value of @desktop_file's "Name" key.
+ *
+ * Return value: the application/link name
+ **/
+const char *
+egg_desktop_file_get_name (EggDesktopFile *desktop_file)
+{
+ return desktop_file->name;
+}
+
+/**
+ * egg_desktop_file_get_icon:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Gets the value of @desktop_file's "Icon" key.
+ *
+ * If the icon string is a full path (that is, if g_path_is_absolute()
+ * returns %TRUE when called on it), it points to a file containing an
+ * unthemed icon. If the icon string is not a full path, it is the
+ * name of a themed icon, which can be looked up with %GtkIconTheme,
+ * or passed directly to a theme-aware widget like %GtkImage or
+ * %GtkCellRendererPixbuf.
+ *
+ * Return value: the icon path or name
+ **/
+const char *
+egg_desktop_file_get_icon (EggDesktopFile *desktop_file)
+{
+ return desktop_file->icon;
+}
+
+gboolean
+egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+char *
+egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error)
+{
+ return g_key_file_get_locale_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, locale,
+ error);
+}
+
+gboolean
+egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+ return g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+}
+
+double
+egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error)
+{
+#if 0
+ return g_key_file_get_double (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ error);
+#else
+ return 0.0;
+#endif
+}
+
+char **
+egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key, length,
+ error);
+}
+
+char **
+egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error)
+{
+ return g_key_file_get_locale_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP, key,
+ locale, length,
+ error);
+}
+
+/**
+ * egg_desktop_file_can_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @desktop_environment: the name of the running desktop environment,
+ * or %NULL
+ *
+ * Tests if @desktop_file can/should be launched in the current
+ * environment. If @desktop_environment is non-%NULL, @desktop_file's
+ * "OnlyShowIn" and "NotShowIn" keys are checked to make sure that
+ * this desktop_file is appropriate for the named environment.
+ *
+ * Furthermore, if @desktop_file has type
+ * %EGG_DESKTOP_FILE_TYPE_APPLICATION, its "TryExec" key (if any) is
+ * also checked, to make sure the binary it points to exists.
+ *
+ * egg_desktop_file_can_launch() does NOT check the value of the
+ * "Hidden" key.
+ *
+ * Return value: %TRUE if @desktop_file can be launched
+ **/
+gboolean
+egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment)
+{
+ char *try_exec, *found_program;
+ char **only_show_in, **not_show_in;
+ gboolean found;
+ int i;
+
+ if (desktop_file->type != EGG_DESKTOP_FILE_TYPE_APPLICATION &&
+ desktop_file->type != EGG_DESKTOP_FILE_TYPE_LINK)
+ return FALSE;
+
+ if (desktop_environment)
+ {
+ only_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN,
+ NULL, NULL);
+ if (only_show_in)
+ {
+ for (i = 0, found = FALSE; only_show_in[i] && !found; i++)
+ {
+ if (!strcmp (only_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (only_show_in);
+
+ if (!found)
+ return FALSE;
+ }
+
+ not_show_in = g_key_file_get_string_list (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN,
+ NULL, NULL);
+ if (not_show_in)
+ {
+ for (i = 0, found = FALSE; not_show_in[i] && !found; i++)
+ {
+ if (!strcmp (not_show_in[i], desktop_environment))
+ found = TRUE;
+ }
+
+ g_strfreev (not_show_in);
+
+ if (found)
+ return FALSE;
+ }
+ }
+
+ if (desktop_file->type == EGG_DESKTOP_FILE_TYPE_APPLICATION)
+ {
+ try_exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TRY_EXEC,
+ NULL);
+ if (try_exec)
+ {
+ found_program = g_find_program_in_path (try_exec);
+ g_free (try_exec);
+
+ if (!found_program)
+ return FALSE;
+ g_free (found_program);
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * egg_desktop_file_accepts_documents:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file represents an application that can accept
+ * documents on the command line.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file)
+{
+ return desktop_file->document_code != 0;
+}
+
+/**
+ * egg_desktop_file_accepts_multiple:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept multiple documents at once.
+ *
+ * If this returns %FALSE, you can still pass multiple documents to
+ * egg_desktop_file_launch(), but that will result in multiple copies
+ * of the application being launched. See egg_desktop_file_launch()
+ * for more details.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'F' ||
+ desktop_file->document_code == 'U');
+}
+
+/**
+ * egg_desktop_file_accepts_uris:
+ * @desktop_file: an #EggDesktopFile
+ *
+ * Tests if @desktop_file can accept (non-"file:") URIs as documents to
+ * open.
+ *
+ * Return value: %TRUE or %FALSE
+ **/
+gboolean
+egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file)
+{
+ return (desktop_file->document_code == 'U' ||
+ desktop_file->document_code == 'u');
+}
+
+static void
+append_quoted_word (GString *str,
+ const char *s,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ const char *p;
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "\"'");
+
+ if (!strchr (s, '\''))
+ g_string_append (str, s);
+ else
+ {
+ for (p = s; *p != '\0'; p++)
+ {
+ if (*p == '\'')
+ g_string_append (str, "'\\''");
+ else
+ g_string_append_c (str, *p);
+ }
+ }
+
+ if (!in_single_quotes && !in_double_quotes)
+ g_string_append_c (str, '\'');
+ else if (!in_single_quotes && in_double_quotes)
+ g_string_append (str, "'\"");
+}
+
+static void
+do_percent_subst (EggDesktopFile *desktop_file,
+ char code,
+ GString *str,
+ GSList **documents,
+ gboolean in_single_quotes,
+ gboolean in_double_quotes)
+{
+ GSList *d;
+ char *doc;
+
+ switch (code)
+ {
+ case '%':
+ g_string_append_c (str, '%');
+ break;
+
+ case 'F':
+ case 'U':
+ for (d = *documents; d; d = d->next)
+ {
+ doc = d->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ }
+ *documents = NULL;
+ break;
+
+ case 'f':
+ case 'u':
+ if (*documents)
+ {
+ doc = (*documents)->data;
+ g_string_append (str, " ");
+ append_quoted_word (str, doc, in_single_quotes, in_double_quotes);
+ *documents = (*documents)->next;
+ }
+ break;
+
+ case 'i':
+ if (desktop_file->icon)
+ {
+ g_string_append (str, "--icon ");
+ append_quoted_word (str, desktop_file->icon,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'c':
+ if (desktop_file->name)
+ {
+ append_quoted_word (str, desktop_file->name,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'k':
+ if (desktop_file->source)
+ {
+ append_quoted_word (str, desktop_file->source,
+ in_single_quotes, in_double_quotes);
+ }
+ break;
+
+ case 'D':
+ case 'N':
+ case 'd':
+ case 'n':
+ case 'v':
+ case 'm':
+ /* Deprecated; skip */
+ break;
+
+ default:
+ g_warning ("Unrecognized %%-code '%%%c' in Exec", code);
+ break;
+ }
+}
+
+static char *
+parse_exec (EggDesktopFile *desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *exec, *p, *command;
+ gboolean escape, single_quot, double_quot;
+ GString *gs;
+
+ exec = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ error);
+ if (!exec)
+ return NULL;
+
+ /* Build the command */
+ gs = g_string_new (NULL);
+ escape = single_quot = double_quot = FALSE;
+
+ for (p = exec; *p != '\0'; p++)
+ {
+ if (escape)
+ {
+ escape = FALSE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\\')
+ {
+ if (!single_quot)
+ escape = TRUE;
+ g_string_append_c (gs, *p);
+ }
+ else if (*p == '\'')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ single_quot = TRUE;
+ else if (single_quot)
+ single_quot = FALSE;
+ }
+ else if (*p == '"')
+ {
+ g_string_append_c (gs, *p);
+ if (!single_quot && !double_quot)
+ double_quot = TRUE;
+ else if (double_quot)
+ double_quot = FALSE;
+ }
+ else if (*p == '%' && p[1])
+ {
+ do_percent_subst (desktop_file, p[1], gs, documents,
+ single_quot, double_quot);
+ p++;
+ }
+ else
+ g_string_append_c (gs, *p);
+ }
+
+ g_free (exec);
+ command = g_string_free (gs, FALSE);
+
+ /* Prepend "xdg-terminal " if needed (FIXME: use gvfs) */
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ NULL))
+ {
+ GError *terminal_error = NULL;
+ gboolean use_terminal =
+ g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TERMINAL,
+ &terminal_error);
+ if (terminal_error)
+ {
+ g_free (command);
+ g_propagate_error (error, terminal_error);
+ return NULL;
+ }
+
+ if (use_terminal)
+ {
+ gs = g_string_new ("xdg-terminal ");
+ append_quoted_word (gs, command, FALSE, FALSE);
+ g_free (command);
+ command = g_string_free (gs, FALSE);
+ }
+ }
+
+ return command;
+}
+
+static GSList *
+translate_document_list (EggDesktopFile *desktop_file, GSList *documents)
+{
+ gboolean accepts_uris = egg_desktop_file_accepts_uris (desktop_file);
+ GSList *ret, *d;
+
+ for (d = documents, ret = NULL; d; d = d->next)
+ {
+ const char *document = d->data;
+ gboolean is_uri = !g_path_is_absolute (document);
+ char *translated;
+
+ if (accepts_uris)
+ {
+ if (is_uri)
+ translated = g_strdup (document);
+ else
+ translated = g_filename_to_uri (document, NULL, NULL);
+ }
+ else
+ {
+ if (is_uri)
+ translated = g_filename_from_uri (document, NULL, NULL);
+ else
+ translated = g_strdup (document);
+ }
+
+ if (translated)
+ ret = g_slist_prepend (ret, translated);
+ }
+
+ return g_slist_reverse (ret);
+}
+
+static void
+free_document_list (GSList *documents)
+{
+ GSList *d;
+
+ for (d = documents; d; d = d->next)
+ g_free (d->data);
+ g_slist_free (documents);
+}
+
+/**
+ * egg_desktop_file_parse_exec:
+ * @desktop_file: a #EggDesktopFile
+ * @documents: a list of document paths or URIs
+ * @error: error pointer
+ *
+ * Parses @desktop_file's Exec key, inserting @documents into it, and
+ * returns the result.
+ *
+ * If @documents contains non-file: URIs and @desktop_file does not
+ * accept URIs, those URIs will be ignored. Likewise, if @documents
+ * contains more elements than @desktop_file accepts, the extra
+ * documents will be ignored.
+ *
+ * Return value: the parsed Exec string
+ **/
+char *
+egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error)
+{
+ GSList *translated, *docs;
+ char *command;
+
+ docs = translated = translate_document_list (desktop_file, documents);
+ command = parse_exec (desktop_file, &docs, error);
+ free_document_list (translated);
+
+ return command;
+}
+
+static gboolean
+parse_link (EggDesktopFile *desktop_file,
+ EggDesktopFile **app_desktop_file,
+ GSList **documents,
+ GError **error)
+{
+ char *url;
+ GKeyFile *key_file;
+
+ url = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_URL,
+ error);
+ if (!url)
+ return FALSE;
+ *documents = g_slist_prepend (NULL, url);
+
+ /* FIXME: use gvfs */
+ key_file = g_key_file_new ();
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_NAME,
+ "xdg-open");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_TYPE,
+ "Application");
+ g_key_file_set_string (key_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ "xdg-open %u");
+ *app_desktop_file = egg_desktop_file_new_from_key_file (key_file, NULL, NULL);
+ return TRUE;
+}
+
+#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE
+static char *
+start_startup_notification (GdkDisplay *display,
+ EggDesktopFile *desktop_file,
+ const char *argv0,
+ int screen,
+ int workspace,
+ guint32 launch_time)
+{
+ static int sequence = 0;
+ char *startup_id;
+ char *description, *wmclass;
+ char *screen_str, *workspace_str;
+
+ if (g_key_file_has_key (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ {
+ if (!g_key_file_get_boolean (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY,
+ NULL))
+ return NULL;
+ wmclass = NULL;
+ }
+ else
+ {
+ wmclass = g_key_file_get_string (desktop_file->key_file,
+ EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS,
+ NULL);
+ if (!wmclass)
+ return NULL;
+ }
+
+ if (launch_time == (guint32)-1)
+ launch_time = gdk_x11_display_get_user_time (display);
+ startup_id = g_strdup_printf ("%s-%lu-%s-%s-%d_TIME%lu",
+ g_get_prgname (),
+ (unsigned long)getpid (),
+ g_get_host_name (),
+ argv0,
+ sequence++,
+ (unsigned long)launch_time);
+
+ description = g_strdup_printf (_("Starting %s"), desktop_file->name);
+ screen_str = g_strdup_printf ("%d", screen);
+ workspace_str = workspace == -1 ? NULL : g_strdup_printf ("%d", workspace);
+
+ gdk_x11_display_broadcast_startup_message (display, "new",
+ "ID", startup_id,
+ "NAME", desktop_file->name,
+ "SCREEN", screen_str,
+ "BIN", argv0,
+ "ICON", desktop_file->icon,
+ "DESKTOP", workspace_str,
+ "DESCRIPTION", description,
+ "WMCLASS", wmclass,
+ NULL);
+
+ g_free (description);
+ g_free (wmclass);
+ g_free (screen_str);
+ g_free (workspace_str);
+
+ return startup_id;
+}
+
+static void
+end_startup_notification (GdkDisplay *display,
+ const char *startup_id)
+{
+ gdk_x11_display_broadcast_startup_message (display, "remove",
+ "ID", startup_id,
+ NULL);
+}
+
+#define EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH (30 /* seconds */ * 1000)
+
+typedef struct {
+ GdkDisplay *display;
+ char *startup_id;
+} StartupNotificationData;
+
+static gboolean
+startup_notification_timeout (gpointer data)
+{
+ StartupNotificationData *sn_data = data;
+
+ end_startup_notification (sn_data->display, sn_data->startup_id);
+ g_object_unref (sn_data->display);
+ g_free (sn_data->startup_id);
+ g_free (sn_data);
+
+ return FALSE;
+}
+
+static void
+set_startup_notification_timeout (GdkDisplay *display,
+ const char *startup_id)
+{
+ StartupNotificationData *sn_data;
+
+ sn_data = g_new (StartupNotificationData, 1);
+ sn_data->display = g_object_ref (display);
+ sn_data->startup_id = g_strdup (startup_id);
+
+ g_timeout_add (EGG_DESKTOP_FILE_SN_TIMEOUT_LENGTH,
+ startup_notification_timeout, sn_data);
+}
+#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */
+
+extern char **environ;
+
+static GPtrArray *
+array_putenv (GPtrArray *env, char *variable)
+{
+ int i, keylen;
+
+ if (!env)
+ {
+ env = g_ptr_array_new ();
+
+ for (i = 0; environ[i]; i++)
+ g_ptr_array_add (env, g_strdup (environ[i]));
+ }
+
+ keylen = strcspn (variable, "=");
+
+ /* Remove old value of key */
+ for (i = 0; i < env->len; i++)
+ {
+ char *envvar = env->pdata[i];
+
+ if (!strncmp (envvar, variable, keylen) && envvar[keylen] == '=')
+ {
+ g_free (envvar);
+ g_ptr_array_remove_index_fast (env, i);
+ break;
+ }
+ }
+
+ /* Add new value */
+ g_ptr_array_add (env, g_strdup (variable));
+
+ return env;
+}
+
+static gboolean
+egg_desktop_file_launchv (EggDesktopFile *desktop_file,
+ GSList *documents, va_list args,
+ GError **error)
+{
+ EggDesktopFileLaunchOption option;
+ GSList *translated_documents = NULL, *docs;
+ char *command, **argv;
+ int argc, i, screen_num;
+ gboolean success, current_success;
+ GdkDisplay *display;
+ char *startup_id;
+
+ GPtrArray *env = NULL;
+ char **variables = NULL;
+ GdkScreen *screen = NULL;
+ int workspace = -1;
+ const char *directory = NULL;
+ guint32 launch_time = (guint32)-1;
+ GSpawnFlags flags = G_SPAWN_SEARCH_PATH;
+ GSpawnChildSetupFunc setup_func = NULL;
+ gpointer setup_data = NULL;
+
+ GPid *ret_pid = NULL;
+ int *ret_stdin = NULL, *ret_stdout = NULL, *ret_stderr = NULL;
+ char **ret_startup_id = NULL;
+
+ if (documents && desktop_file->document_code == 0)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Application does not accept documents on command line"));
+ return FALSE;
+ }
+
+ /* Read the options: technically it's incorrect for the caller to
+ * NULL-terminate the list of options (rather than 0-terminating
+ * it), but NULL-terminating lets us use G_GNUC_NULL_TERMINATED,
+ * it's more consistent with other glib/gtk methods, and it will
+ * work as long as sizeof (int) <= sizeof (NULL), and NULL is
+ * represented as 0. (Which is true everywhere we care about.)
+ */
+ while ((option = va_arg (args, EggDesktopFileLaunchOption)))
+ {
+ switch (option)
+ {
+ case EGG_DESKTOP_FILE_LAUNCH_CLEARENV:
+ if (env)
+ g_ptr_array_free (env, TRUE);
+ env = g_ptr_array_new ();
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_PUTENV:
+ variables = va_arg (args, char **);
+ for (i = 0; variables[i]; i++)
+ env = array_putenv (env, variables[i]);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_SCREEN:
+ screen = va_arg (args, GdkScreen *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_WORKSPACE:
+ workspace = va_arg (args, int);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_DIRECTORY:
+ directory = va_arg (args, const char *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_TIME:
+ launch_time = va_arg (args, guint32);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_FLAGS:
+ flags |= va_arg (args, GSpawnFlags);
+ /* Make sure they didn't set any flags that don't make sense. */
+ flags &= ~G_SPAWN_FILE_AND_ARGV_ZERO;
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC:
+ setup_func = va_arg (args, GSpawnChildSetupFunc);
+ setup_data = va_arg (args, gpointer);
+ break;
+
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_PID:
+ ret_pid = va_arg (args, GPid *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE:
+ ret_stdin = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE:
+ ret_stdout = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE:
+ ret_stderr = va_arg (args, int *);
+ break;
+ case EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID:
+ ret_startup_id = va_arg (args, char **);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
+ _("Unrecognized launch option: %d"),
+ GPOINTER_TO_INT (option));
+ success = FALSE;
+ goto out;
+ }
+ }
+
+ if (screen)
+ {
+ char *display_name = gdk_screen_make_display_name (screen);
+ char *display_env = g_strdup_printf ("DISPLAY=%s", display_name);
+ env = array_putenv (env, display_env);
+ g_free (display_name);
+ g_free (display_env);
+
+ display = gdk_screen_get_display (screen);
+ }
+ else
+ {
+ display = gdk_display_get_default ();
+ screen = gdk_display_get_default_screen (display);
+ }
+ screen_num = gdk_screen_get_number (screen);
+
+ translated_documents = translate_document_list (desktop_file, documents);
+ docs = translated_documents;
+
+ success = FALSE;
+
+ do
+ {
+ command = parse_exec (desktop_file, &docs, error);
+ if (!command)
+ goto out;
+
+ if (!g_shell_parse_argv (command, &argc, &argv, error))
+ {
+ g_free (command);
+ goto out;
+ }
+ g_free (command);
+
+#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE
+ startup_id = start_startup_notification (display, desktop_file,
+ argv[0], screen_num,
+ workspace, launch_time);
+ if (startup_id)
+ {
+ char *startup_id_env = g_strdup_printf ("DESKTOP_STARTUP_ID=%s",
+ startup_id);
+ env = array_putenv (env, startup_id_env);
+ g_free (startup_id_env);
+ }
+#else
+ startup_id = NULL;
+#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */
+
+ if (env != NULL)
+ {
+ /* Add NULL item in the end of array */
+ g_ptr_array_add (env, NULL);
+ }
+
+ current_success =
+ g_spawn_async_with_pipes (directory,
+ argv,
+ env ? (char **)(env->pdata) : NULL,
+ flags,
+ setup_func, setup_data,
+ ret_pid,
+ ret_stdin, ret_stdout, ret_stderr,
+ error);
+ g_strfreev (argv);
+
+ if (startup_id)
+ {
+#ifdef HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE
+ if (current_success)
+ {
+ set_startup_notification_timeout (display, startup_id);
+
+ if (ret_startup_id)
+ *ret_startup_id = startup_id;
+ else
+ g_free (startup_id);
+ }
+ else
+#endif /* HAVE_GDK_X11_DISPLAY_BROADCAST_STARTUP_MESSAGE */
+ g_free (startup_id);
+ }
+ else if (ret_startup_id)
+ *ret_startup_id = NULL;
+
+ if (current_success)
+ {
+ /* If we successfully launch any instances of the app, make
+ * sure we return TRUE and don't set @error.
+ */
+ success = TRUE;
+ error = NULL;
+
+ /* Also, only set the output params on the first one */
+ ret_pid = NULL;
+ ret_stdin = ret_stdout = ret_stderr = NULL;
+ ret_startup_id = NULL;
+ }
+ }
+ while (docs && current_success);
+
+ out:
+ if (env)
+ {
+ g_ptr_array_free (env, TRUE);
+ }
+ free_document_list (translated_documents);
+
+ return success;
+}
+
+/**
+ * egg_desktop_file_launch:
+ * @desktop_file: an #EggDesktopFile
+ * @documents: a list of URIs or paths to documents to open
+ * @error: error pointer
+ * @...: additional options
+ *
+ * Launches @desktop_file with the given arguments. Additional options
+ * can be specified as follows:
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_CLEARENV: (no arguments)
+ * clears the environment in the child process
+ * %EGG_DESKTOP_FILE_LAUNCH_PUTENV: (char **variables)
+ * adds the NAME=VALUE strings in the given %NULL-terminated
+ * array to the child process's environment
+ * %EGG_DESKTOP_FILE_LAUNCH_SCREEN: (GdkScreen *screen)
+ * causes the application to be launched on the given screen
+ * %EGG_DESKTOP_FILE_LAUNCH_WORKSPACE: (int workspace)
+ * causes the application to be launched on the given workspace
+ * %EGG_DESKTOP_FILE_LAUNCH_DIRECTORY: (char *dir)
+ * causes the application to be launched in the given directory
+ * %EGG_DESKTOP_FILE_LAUNCH_TIME: (guint32 launch_time)
+ * sets the "launch time" for the application. If the user
+ * interacts with another window after @launch_time but before
+ * the launched application creates its first window, the window
+ * manager may choose to not give focus to the new application.
+ * Passing 0 for @launch_time will explicitly request that the
+ * application not receive focus.
+ * %EGG_DESKTOP_FILE_LAUNCH_FLAGS (GSpawnFlags flags)
+ * Sets additional #GSpawnFlags to use. See g_spawn_async() for
+ * more details.
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC (GSpawnChildSetupFunc, gpointer)
+ * Sets the child setup callback and the data to pass to it.
+ * (See g_spawn_async() for more details.)
+ *
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID (GPid **pid)
+ * On a successful launch, sets *@pid to the PID of the launched
+ * application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID (char **startup_id)
+ * On a successful launch, sets *@startup_id to the Startup
+ * Notification "startup id" of the launched application.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stdin.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stdout.
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE (int *fd)
+ * On a successful launch, sets *@fd to the file descriptor of
+ * a pipe connected to the application's stderr.
+ *
+ * The options should be terminated with a single %NULL.
+ *
+ * If @documents contains multiple documents, but
+ * egg_desktop_file_accepts_multiple() returns %FALSE for
+ * @desktop_file, then egg_desktop_file_launch() will actually launch
+ * multiple instances of the application. In that case, the return
+ * value (as well as any values passed via
+ * %EGG_DESKTOP_FILE_LAUNCH_RETURN_PID, etc) will only reflect the
+ * first instance of the application that was launched (but the
+ * %EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC will be called for each
+ * instance).
+ *
+ * Return value: %TRUE if the application was successfully launched.
+ **/
+gboolean
+egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents, GError **error,
+ ...)
+{
+ va_list args;
+ gboolean success;
+ EggDesktopFile *app_desktop_file;
+
+ switch (desktop_file->type)
+ {
+ case EGG_DESKTOP_FILE_TYPE_APPLICATION:
+ va_start (args, error);
+ success = egg_desktop_file_launchv (desktop_file, documents,
+ args, error);
+ va_end (args);
+ break;
+
+ case EGG_DESKTOP_FILE_TYPE_LINK:
+ if (documents)
+ {
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Can't pass document URIs to a 'Type=Link' desktop entry"));
+ return FALSE;
+ }
+
+ if (!parse_link (desktop_file, &app_desktop_file, &documents, error))
+ return FALSE;
+
+ va_start (args, error);
+ success = egg_desktop_file_launchv (app_desktop_file, documents,
+ args, error);
+ va_end (args);
+
+ egg_desktop_file_free (app_desktop_file);
+ free_document_list (documents);
+ break;
+
+ default:
+ g_set_error (error, EGG_DESKTOP_FILE_ERROR,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ _("Not a launchable item"));
+ success = FALSE;
+ break;
+ }
+
+ return success;
+}
+
+
+GQuark
+egg_desktop_file_error_quark (void)
+{
+ return g_quark_from_static_string ("egg-desktop_file-error-quark");
+}
+
+
+G_LOCK_DEFINE_STATIC (egg_desktop_file);
+static EggDesktopFile *egg_desktop_file;
+
+/**
+ * egg_set_desktop_file:
+ * @desktop_file_path: path to the application's desktop file
+ *
+ * Creates an #EggDesktopFile for the application from the data at
+ * @desktop_file_path. This will also call g_set_application_name()
+ * with the localized application name from the desktop file, and
+ * gtk_window_set_default_icon_name() or
+ * gtk_window_set_default_icon_from_file() with the application's
+ * icon. Other code may use additional information from the desktop
+ * file.
+ *
+ * Note that for thread safety reasons, this function can only
+ * be called once.
+ **/
+void
+egg_set_desktop_file (const char *desktop_file_path)
+{
+ GError *error = NULL;
+
+ G_LOCK (egg_desktop_file);
+ if (egg_desktop_file)
+ egg_desktop_file_free (egg_desktop_file);
+
+ egg_desktop_file = egg_desktop_file_new (desktop_file_path, &error);
+ if (error)
+ {
+ g_warning ("Could not load desktop file '%s': %s",
+ desktop_file_path, error->message);
+ g_error_free (error);
+ }
+
+ /* Set localized application name and default window icon */
+ if (egg_desktop_file->name)
+ g_set_application_name (egg_desktop_file->name);
+ if (egg_desktop_file->icon)
+ {
+ if (g_path_is_absolute (egg_desktop_file->icon))
+ gtk_window_set_default_icon_from_file (egg_desktop_file->icon, NULL);
+ else
+ gtk_window_set_default_icon_name (egg_desktop_file->icon);
+ }
+
+ G_UNLOCK (egg_desktop_file);
+}
+
+/**
+ * egg_get_desktop_file:
+ *
+ * Gets the application's #EggDesktopFile, as set by
+ * egg_set_desktop_file().
+ *
+ * Return value: the #EggDesktopFile, or %NULL if it hasn't been set.
+ **/
+EggDesktopFile *
+egg_get_desktop_file (void)
+{
+ EggDesktopFile *retval;
+
+ G_LOCK (egg_desktop_file);
+ retval = egg_desktop_file;
+ G_UNLOCK (egg_desktop_file);
+
+ return retval;
+}
diff --git a/src/sugar/eggdesktopfile.h b/src/sugar/eggdesktopfile.h
new file mode 100644
index 0000000..270aec8
--- /dev/null
+++ b/src/sugar/eggdesktopfile.h
@@ -0,0 +1,156 @@
+/* eggdesktopfile.h - Freedesktop.Org Desktop Files
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; see the file COPYING.LIB. If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place -
+ * Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_DESKTOP_FILE_H__
+#define __EGG_DESKTOP_FILE_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct EggDesktopFile EggDesktopFile;
+
+typedef enum {
+ EGG_DESKTOP_FILE_TYPE_UNRECOGNIZED,
+
+ EGG_DESKTOP_FILE_TYPE_APPLICATION,
+ EGG_DESKTOP_FILE_TYPE_LINK,
+ EGG_DESKTOP_FILE_TYPE_DIRECTORY,
+} EggDesktopFileType;
+
+EggDesktopFile *egg_desktop_file_new (const char *desktop_file_path,
+ GError **error);
+
+EggDesktopFile *egg_desktop_file_new_from_data_dirs (const char *desktop_file_path,
+ GError **error);
+EggDesktopFile *egg_desktop_file_new_from_key_file (GKeyFile *key_file,
+ const char *source,
+ GError **error);
+
+void egg_desktop_file_free (EggDesktopFile *desktop_file);
+
+const char *egg_desktop_file_get_source (EggDesktopFile *desktop_file) G_GNUC_PURE;
+
+EggDesktopFileType egg_desktop_file_get_desktop_file_type (EggDesktopFile *desktop_file) G_GNUC_PURE;
+
+const char *egg_desktop_file_get_name (EggDesktopFile *desktop_file) G_GNUC_PURE;
+const char *egg_desktop_file_get_icon (EggDesktopFile *desktop_file) G_GNUC_PURE;
+
+gboolean egg_desktop_file_can_launch (EggDesktopFile *desktop_file,
+ const char *desktop_environment);
+
+gboolean egg_desktop_file_accepts_documents (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_multiple (EggDesktopFile *desktop_file);
+gboolean egg_desktop_file_accepts_uris (EggDesktopFile *desktop_file);
+
+char *egg_desktop_file_parse_exec (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error);
+
+gboolean egg_desktop_file_launch (EggDesktopFile *desktop_file,
+ GSList *documents,
+ GError **error,
+ ...) G_GNUC_NULL_TERMINATED;
+
+typedef enum {
+ EGG_DESKTOP_FILE_LAUNCH_CLEARENV = 1,
+ EGG_DESKTOP_FILE_LAUNCH_PUTENV,
+ EGG_DESKTOP_FILE_LAUNCH_SCREEN,
+ EGG_DESKTOP_FILE_LAUNCH_WORKSPACE,
+ EGG_DESKTOP_FILE_LAUNCH_DIRECTORY,
+ EGG_DESKTOP_FILE_LAUNCH_TIME,
+ EGG_DESKTOP_FILE_LAUNCH_FLAGS,
+ EGG_DESKTOP_FILE_LAUNCH_SETUP_FUNC,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_PID,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDIN_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDOUT_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STDERR_PIPE,
+ EGG_DESKTOP_FILE_LAUNCH_RETURN_STARTUP_ID
+} EggDesktopFileLaunchOption;
+
+/* Standard Keys */
+#define EGG_DESKTOP_FILE_GROUP "Desktop Entry"
+
+#define EGG_DESKTOP_FILE_KEY_TYPE "Type"
+#define EGG_DESKTOP_FILE_KEY_VERSION "Version"
+#define EGG_DESKTOP_FILE_KEY_NAME "Name"
+#define EGG_DESKTOP_FILE_KEY_GENERIC_NAME "GenericName"
+#define EGG_DESKTOP_FILE_KEY_NO_DISPLAY "NoDisplay"
+#define EGG_DESKTOP_FILE_KEY_COMMENT "Comment"
+#define EGG_DESKTOP_FILE_KEY_ICON "Icon"
+#define EGG_DESKTOP_FILE_KEY_HIDDEN "Hidden"
+#define EGG_DESKTOP_FILE_KEY_ONLY_SHOW_IN "OnlyShowIn"
+#define EGG_DESKTOP_FILE_KEY_NOT_SHOW_IN "NotShowIn"
+#define EGG_DESKTOP_FILE_KEY_TRY_EXEC "TryExec"
+#define EGG_DESKTOP_FILE_KEY_EXEC "Exec"
+#define EGG_DESKTOP_FILE_KEY_PATH "Path"
+#define EGG_DESKTOP_FILE_KEY_TERMINAL "Terminal"
+#define EGG_DESKTOP_FILE_KEY_MIME_TYPE "MimeType"
+#define EGG_DESKTOP_FILE_KEY_CATEGORIES "Categories"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_NOTIFY "StartupNotify"
+#define EGG_DESKTOP_FILE_KEY_STARTUP_WM_CLASS "StartupWMClass"
+#define EGG_DESKTOP_FILE_KEY_URL "URL"
+
+/* Accessors */
+gboolean egg_desktop_file_has_key (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char *egg_desktop_file_get_string (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error) G_GNUC_MALLOC;
+char *egg_desktop_file_get_locale_string (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ GError **error) G_GNUC_MALLOC;
+gboolean egg_desktop_file_get_boolean (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+double egg_desktop_file_get_numeric (EggDesktopFile *desktop_file,
+ const char *key,
+ GError **error);
+char **egg_desktop_file_get_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+char **egg_desktop_file_get_locale_string_list (EggDesktopFile *desktop_file,
+ const char *key,
+ const char *locale,
+ gsize *length,
+ GError **error) G_GNUC_MALLOC;
+
+
+/* Errors */
+#define EGG_DESKTOP_FILE_ERROR egg_desktop_file_error_quark()
+
+GQuark egg_desktop_file_error_quark (void);
+
+typedef enum {
+ EGG_DESKTOP_FILE_ERROR_INVALID,
+ EGG_DESKTOP_FILE_ERROR_NOT_LAUNCHABLE,
+ EGG_DESKTOP_FILE_ERROR_UNRECOGNIZED_OPTION,
+} EggDesktopFileError;
+
+/* Global application desktop file */
+void egg_set_desktop_file (const char *desktop_file_path);
+EggDesktopFile *egg_get_desktop_file (void);
+
+
+G_END_DECLS
+
+#endif /* __EGG_DESKTOP_FILE_H__ */
diff --git a/src/sugar/eggsmclient-private.h b/src/sugar/eggsmclient-private.h
new file mode 100644
index 0000000..0b4ec40
--- /dev/null
+++ b/src/sugar/eggsmclient-private.h
@@ -0,0 +1,54 @@
+/* eggsmclient-private.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_SM_CLIENT_PRIVATE_H__
+#define __EGG_SM_CLIENT_PRIVATE_H__
+
+#include <gdkconfig.h>
+#include "eggsmclient.h"
+
+G_BEGIN_DECLS
+
+GKeyFile *egg_sm_client_save_state (EggSMClient *client);
+void egg_sm_client_quit_requested (EggSMClient *client);
+void egg_sm_client_quit_cancelled (EggSMClient *client);
+void egg_sm_client_quit (EggSMClient *client);
+
+#if defined (GDK_WINDOWING_X11)
+# ifdef EGG_SM_CLIENT_BACKEND_XSMP
+#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
+GType egg_sm_client_xsmp_get_type (void);
+EggSMClient *egg_sm_client_xsmp_new (void);
+# endif
+# ifdef EGG_SM_CLIENT_BACKEND_DBUS
+GType egg_sm_client_dbus_get_type (void);
+EggSMClient *egg_sm_client_dbus_new (void);
+# endif
+#elif defined (GDK_WINDOWING_WIN32)
+GType egg_sm_client_win32_get_type (void);
+EggSMClient *egg_sm_client_win32_new (void);
+#elif defined (GDK_WINDOWING_QUARTZ)
+GType egg_sm_client_osx_get_type (void);
+EggSMClient *egg_sm_client_osx_new (void);
+#endif
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_PRIVATE_H__ */
diff --git a/src/sugar/eggsmclient-xsmp.c b/src/sugar/eggsmclient-xsmp.c
new file mode 100644
index 0000000..13eb5d5
--- /dev/null
+++ b/src/sugar/eggsmclient-xsmp.c
@@ -0,0 +1,1359 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * Inspired by various other pieces of code including GsmClient (C)
+ * 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm
+ * session code (C) 1998 The Open Group.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+#include "eggdesktopfile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <X11/SM/SMlib.h>
+
+#include <gdk/gdk.h>
+
+#define EGG_TYPE_SM_CLIENT_XSMP (egg_sm_client_xsmp_get_type ())
+#define EGG_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMP))
+#define EGG_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+#define EGG_IS_SM_CLIENT_XSMP(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_IS_SM_CLIENT_XSMP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT_XSMP))
+#define EGG_SM_CLIENT_XSMP_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT_XSMP, EggSMClientXSMPClass))
+
+typedef struct _EggSMClientXSMP EggSMClientXSMP;
+typedef struct _EggSMClientXSMPClass EggSMClientXSMPClass;
+
+/* These mostly correspond to the similarly-named states in section
+ * 9.1 of the XSMP spec. Some of the states there aren't represented
+ * here, because we don't need them. SHUTDOWN_CANCELLED is slightly
+ * different from the spec; we use it when the client is IDLE after a
+ * ShutdownCancelled message, but the application is still interacting
+ * and doesn't know the shutdown has been cancelled yet.
+ */
+typedef enum
+{
+ XSMP_STATE_START,
+ XSMP_STATE_IDLE,
+ XSMP_STATE_SAVE_YOURSELF,
+ XSMP_STATE_INTERACT_REQUEST,
+ XSMP_STATE_INTERACT,
+ XSMP_STATE_SAVE_YOURSELF_DONE,
+ XSMP_STATE_SHUTDOWN_CANCELLED,
+ XSMP_STATE_CONNECTION_CLOSED,
+} EggSMClientXSMPState;
+
+static const char *state_names[] = {
+ "start",
+ "idle",
+ "save-yourself",
+ "interact-request",
+ "interact",
+ "save-yourself-done",
+ "shutdown-cancelled",
+ "connection-closed"
+};
+
+#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state])
+
+struct _EggSMClientXSMP
+{
+ EggSMClient parent;
+
+ SmcConn connection;
+ char *client_id;
+
+ EggSMClientXSMPState state;
+ char **restart_command;
+ gboolean set_restart_command;
+ int restart_style;
+
+ guint idle;
+
+ /* Current SaveYourself state */
+ guint expecting_initial_save_yourself : 1;
+ guint need_save_state : 1;
+ guint need_quit_requested : 1;
+ guint interact_errors : 1;
+ guint shutting_down : 1;
+
+ /* Todo list */
+ guint waiting_to_emit_quit : 1;
+ guint waiting_to_emit_quit_cancelled : 1;
+ guint waiting_to_save_myself : 1;
+
+};
+
+struct _EggSMClientXSMPClass
+{
+ EggSMClientClass parent_class;
+
+};
+
+static void sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id);
+static void sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+static void sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit);
+static gboolean sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+static void xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_style,
+ Bool shutdown,
+ int interact_style,
+ Bool fast);
+static void xsmp_die (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data);
+static void xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data);
+
+static SmProp *array_prop (const char *name,
+ ...);
+static SmProp *ptrarray_prop (const char *name,
+ GPtrArray *values);
+static SmProp *string_prop (const char *name,
+ const char *value);
+static SmProp *card8_prop (const char *name,
+ unsigned char value);
+
+static void set_properties (EggSMClientXSMP *xsmp, ...);
+static void delete_properties (EggSMClientXSMP *xsmp, ...);
+
+static GPtrArray *generate_command (char **restart_command,
+ const char *client_id,
+ const char *state_file);
+
+static void save_state (EggSMClientXSMP *xsmp);
+static void do_save_yourself (EggSMClientXSMP *xsmp);
+static void update_pending_events (EggSMClientXSMP *xsmp);
+
+static void ice_init (void);
+static gboolean process_ice_messages (IceConn ice_conn);
+static void smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values);
+
+G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT)
+
+static void
+egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp)
+{
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ xsmp->connection = NULL;
+ xsmp->restart_style = SmRestartIfRunning;
+ xsmp->client_id = NULL;
+}
+
+static void
+egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass)
+{
+ EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
+
+ sm_client_class->startup = sm_client_xsmp_startup;
+ sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command;
+ sm_client_class->will_quit = sm_client_xsmp_will_quit;
+ sm_client_class->end_session = sm_client_xsmp_end_session;
+}
+
+EggSMClient *
+egg_sm_client_xsmp_new (void)
+{
+ if (!g_getenv ("SESSION_MANAGER"))
+ return NULL;
+
+ return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL);
+}
+
+static gboolean
+sm_client_xsmp_connect (gpointer user_data)
+{
+ EggSMClientXSMP *xsmp = user_data;
+ SmcCallbacks callbacks;
+ char *client_id;
+ char error_string_ret[256];
+ char pid_str[64];
+ EggDesktopFile *desktop_file;
+ GPtrArray *clone, *restart;
+
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+
+ ice_init ();
+ SmcSetErrorHandler (smc_error_handler);
+
+ callbacks.save_yourself.callback = xsmp_save_yourself;
+ callbacks.die.callback = xsmp_die;
+ callbacks.save_complete.callback = xsmp_save_complete;
+ callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
+
+ callbacks.save_yourself.client_data = xsmp;
+ callbacks.die.client_data = xsmp;
+ callbacks.save_complete.client_data = xsmp;
+ callbacks.shutdown_cancelled.client_data = xsmp;
+
+ client_id = NULL;
+ error_string_ret[0] = '\0';
+ xsmp->connection =
+ SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
+ SmcSaveYourselfProcMask | SmcDieProcMask |
+ SmcSaveCompleteProcMask |
+ SmcShutdownCancelledProcMask,
+ &callbacks,
+ //xsmp->client_id, &client_id,
+ NULL, &client_id,
+ sizeof (error_string_ret), error_string_ret);
+
+ if (!xsmp->connection)
+ {
+ g_warning ("Failed to connect to the session manager: %s\n",
+ error_string_ret[0] ?
+ error_string_ret : "no error message given");
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+ return FALSE;
+ }
+
+ /* We expect a pointless initial SaveYourself if either (a) we
+ * didn't have an initial client ID, or (b) we DID have an initial
+ * client ID, but the server rejected it and gave us a new one.
+ */
+ if (!xsmp->client_id ||
+ (client_id && strcmp (xsmp->client_id, client_id) != 0))
+ xsmp->expecting_initial_save_yourself = TRUE;
+
+ if (client_id)
+ {
+ g_free (xsmp->client_id);
+ xsmp->client_id = g_strdup (client_id);
+ free (client_id);
+
+ gdk_threads_enter ();
+ gdk_set_sm_client_id (xsmp->client_id);
+ gdk_threads_leave ();
+
+ g_debug ("Got client ID \"%s\"", xsmp->client_id);
+ }
+
+ /* Parse info out of desktop file */
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GError *err = NULL;
+ char *cmdline, **argv;
+ int argc;
+
+ if (xsmp->restart_style == SmRestartIfRunning)
+ {
+ if (egg_desktop_file_get_boolean (desktop_file,
+ "X-GNOME-AutoRestart", NULL))
+ xsmp->restart_style = SmRestartImmediately;
+ }
+
+ if (!xsmp->set_restart_command)
+ {
+ cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
+ if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
+ {
+ egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
+ argc, (const char **)argv);
+ g_strfreev (argv);
+ }
+ else
+ {
+ g_warning ("Could not parse Exec line in desktop file: %s",
+ err->message);
+ g_error_free (err);
+ }
+ }
+ }
+
+ if (!xsmp->set_restart_command)
+ xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
+
+ clone = generate_command (xsmp->restart_command, NULL, NULL);
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+
+ g_debug ("Setting initial properties");
+
+ /* Program, CloneCommand, RestartCommand, and UserID are required.
+ * ProcessID isn't required, but the SM may be able to do something
+ * useful with it.
+ */
+ g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
+ set_properties (xsmp,
+ string_prop (SmProgram, g_get_prgname ()),
+ ptrarray_prop (SmCloneCommand, clone),
+ ptrarray_prop (SmRestartCommand, restart),
+ string_prop (SmUserID, g_get_user_name ()),
+ string_prop (SmProcessID, pid_str),
+ card8_prop (SmRestartStyleHint, xsmp->restart_style),
+ NULL);
+ g_ptr_array_free (clone, TRUE);
+ g_ptr_array_free (restart, TRUE);
+
+ if (desktop_file)
+ {
+ set_properties (xsmp,
+ string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
+ NULL);
+ }
+
+ xsmp->state = XSMP_STATE_IDLE;
+ return FALSE;
+}
+
+/* This gets called from two different places: xsmp_die() (when the
+ * server asks us to disconnect) and process_ice_messages() (when the
+ * server disconnects unexpectedly).
+ */
+static void
+sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
+{
+ SmcConn connection;
+
+ if (!xsmp->connection)
+ return;
+
+ g_debug ("Disconnecting");
+
+ connection = xsmp->connection;
+ xsmp->connection = NULL;
+ SmcCloseConnection (connection, 0, NULL);
+ xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
+
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+}
+
+static void
+sm_client_xsmp_startup (EggSMClient *client,
+ const char *client_id)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+
+ xsmp->state = XSMP_STATE_START;
+ if (xsmp->client_id)
+ g_free (xsmp->client_id);
+ xsmp->client_id = g_strdup (client_id);
+
+ /* Don't connect to the session manager until we reach the main
+ * loop, since the session manager may assume we're fully up and
+ * running once we connect. (This also gives the application a
+ * chance to call egg_set_desktop_file() before we set the initial
+ * properties.)
+ */
+ xsmp->idle = g_idle_add (sm_client_xsmp_connect, client);
+}
+
+static void
+sm_client_xsmp_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int i;
+
+ g_strfreev (xsmp->restart_command);
+
+ xsmp->restart_command = g_new (char *, argc + 1);
+ for (i = 0; i < argc; i++)
+ xsmp->restart_command[i] = g_strdup (argv[i]);
+ xsmp->restart_command[i] = NULL;
+
+ xsmp->set_restart_command = TRUE;
+}
+
+static void
+sm_client_xsmp_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+
+ if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED)
+ {
+ /* The session manager has already exited! Schedule a quit
+ * signal.
+ */
+ xsmp->waiting_to_emit_quit = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* We received a ShutdownCancelled message while the application
+ * was interacting; Schedule a quit_cancelled signal.
+ */
+ xsmp->waiting_to_emit_quit_cancelled = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT);
+
+ g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True");
+ SmcInteractDone (xsmp->connection, !will_quit);
+
+ if (will_quit && xsmp->need_save_state)
+ save_state (xsmp);
+
+ g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False");
+ SmcSaveYourselfDone (xsmp->connection, will_quit);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static gboolean
+sm_client_xsmp_end_session (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation)
+{
+ EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
+ int save_type;
+
+ /* To end the session via XSMP, we have to send a
+ * SaveYourselfRequest. We aren't allowed to do that if anything
+ * else is going on, but we don't want to expose this fact to the
+ * application. So we do our best to patch things up here...
+ *
+ * In the worst case, this method might block for some length of
+ * time in process_ice_messages, but the only time that code path is
+ * honestly likely to get hit is if the application tries to end the
+ * session as the very first thing it does, in which case it
+ * probably won't actually block anyway. It's not worth gunking up
+ * the API to try to deal nicely with the other 0.01% of cases where
+ * this happens.
+ */
+
+ while (xsmp->state != XSMP_STATE_IDLE ||
+ xsmp->expecting_initial_save_yourself)
+ {
+ /* If we're already shutting down, we don't need to do anything. */
+ if (xsmp->shutting_down)
+ return TRUE;
+
+ switch (xsmp->state)
+ {
+ case XSMP_STATE_START:
+ /* Force the connection to complete (or fail) now. */
+ sm_client_xsmp_connect (xsmp);
+ break;
+
+ case XSMP_STATE_CONNECTION_CLOSED:
+ return FALSE;
+
+ case XSMP_STATE_SAVE_YOURSELF:
+ /* Trying to log out from the save_state callback? Whatever.
+ * Abort the save_state.
+ */
+ SmcSaveYourselfDone (xsmp->connection, FALSE);
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ break;
+
+ case XSMP_STATE_INTERACT_REQUEST:
+ case XSMP_STATE_INTERACT:
+ case XSMP_STATE_SHUTDOWN_CANCELLED:
+ /* Already in a shutdown-related state, just ignore
+ * the new shutdown request...
+ */
+ return TRUE;
+
+ case XSMP_STATE_IDLE:
+ if (!xsmp->expecting_initial_save_yourself)
+ break;
+ /* else fall through */
+
+ case XSMP_STATE_SAVE_YOURSELF_DONE:
+ /* We need to wait for some response from the server.*/
+ process_ice_messages (SmcGetIceConnection (xsmp->connection));
+ break;
+
+ default:
+ /* Hm... shouldn't happen */
+ return FALSE;
+ }
+ }
+
+ /* xfce4-session will do the wrong thing if we pass SmSaveGlobal and
+ * the user chooses to save the session. But gnome-session will do
+ * the wrong thing if we pass SmSaveBoth and the user chooses NOT to
+ * save the session... Sigh.
+ */
+ if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session"))
+ save_type = SmSaveBoth;
+ else
+ save_type = SmSaveGlobal;
+
+ g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : "");
+ SmcRequestSaveYourself (xsmp->connection,
+ save_type,
+ True, /* shutdown */
+ SmInteractStyleAny,
+ !request_confirmation, /* fast */
+ True /* global */);
+ return TRUE;
+}
+
+static gboolean
+idle_do_pending_events (gpointer data)
+{
+ EggSMClientXSMP *xsmp = data;
+ EggSMClient *client = data;
+
+ gdk_threads_enter ();
+
+ xsmp->idle = 0;
+
+ if (xsmp->waiting_to_emit_quit)
+ {
+ xsmp->waiting_to_emit_quit = FALSE;
+ egg_sm_client_quit (client);
+ goto out;
+ }
+
+ if (xsmp->waiting_to_emit_quit_cancelled)
+ {
+ xsmp->waiting_to_emit_quit_cancelled = FALSE;
+ egg_sm_client_quit_cancelled (client);
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+
+ if (xsmp->waiting_to_save_myself)
+ {
+ xsmp->waiting_to_save_myself = FALSE;
+ do_save_yourself (xsmp);
+ }
+
+ out:
+ gdk_threads_leave ();
+ return FALSE;
+}
+
+static void
+update_pending_events (EggSMClientXSMP *xsmp)
+{
+ gboolean want_idle =
+ xsmp->waiting_to_emit_quit ||
+ xsmp->waiting_to_emit_quit_cancelled ||
+ xsmp->waiting_to_save_myself;
+
+ if (want_idle)
+ {
+ if (xsmp->idle == 0)
+ xsmp->idle = g_idle_add (idle_do_pending_events, xsmp);
+ }
+ else
+ {
+ if (xsmp->idle != 0)
+ g_source_remove (xsmp->idle);
+ xsmp->idle = 0;
+ }
+}
+
+static void
+fix_broken_state (EggSMClientXSMP *xsmp, const char *message,
+ gboolean send_interact_done,
+ gboolean send_save_yourself_done)
+{
+ g_warning ("Received XSMP %s message in state %s: client or server error",
+ message, EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ /* Forget any pending SaveYourself plans we had */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+
+ if (send_interact_done)
+ SmcInteractDone (xsmp->connection, False);
+ if (send_save_yourself_done)
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE;
+}
+
+/* SM callbacks */
+
+static void
+xsmp_save_yourself (SmcConn smc_conn,
+ SmPointer client_data,
+ int save_type,
+ Bool shutdown,
+ int interact_style,
+ Bool fast)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ gboolean wants_quit_requested;
+
+ g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s",
+ save_type == SmSaveLocal ? "SmSaveLocal" :
+ save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
+ shutdown ? "Shutdown" : "!Shutdown",
+ interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
+ interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
+ "SmInteractStyleNone", fast ? "Fast" : "!Fast",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_IDLE &&
+ xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE);
+ return;
+ }
+
+ /* If this is the initial SaveYourself, ignore it; we've already set
+ * properties and there's no reason to actually save state too.
+ */
+ if (xsmp->expecting_initial_save_yourself)
+ {
+ xsmp->expecting_initial_save_yourself = FALSE;
+
+ if (save_type == SmSaveLocal &&
+ interact_style == SmInteractStyleNone &&
+ !shutdown && !fast)
+ {
+ g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself");
+ SmcSaveYourselfDone (xsmp->connection, True);
+ /* As explained in the comment at the end of
+ * do_save_yourself(), SAVE_YOURSELF_DONE is the correct
+ * state here, not IDLE.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+ return;
+ }
+ else
+ g_warning ("First SaveYourself was not the expected one!");
+ }
+
+ /* Even ignoring the "fast" flag completely, there are still 18
+ * different combinations of save_type, shutdown and interact_style.
+ * We interpret them as follows:
+ *
+ * Type Shutdown Interact Interpretation
+ * G F A/E/N do nothing (1)
+ * G T N do nothing (1)*
+ * G T A/E quit_requested (2)
+ * L/B F A/E/N save_state (3)
+ * L/B T N save_state (3)*
+ * L/B T A/E quit_requested, then save_state (4)
+ *
+ * 1. Do nothing, because the SM asked us to do something
+ * uninteresting (save open files, but then don't quit
+ * afterward) or rude (save open files without asking the user
+ * for confirmation).
+ *
+ * 2. Request interaction and then emit ::quit_requested. This
+ * perhaps isn't quite correct for the SmInteractStyleErrors
+ * case, but we don't care.
+ *
+ * 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these
+ * rows essentially get demoted to SmSaveLocal, because their
+ * Global halves correspond to "do nothing".
+ *
+ * 4. Request interaction, emit ::quit_requested, and then emit
+ * ::save_state after interacting. This is the SmSaveBoth
+ * equivalent of #2, but we also promote SmSaveLocal shutdown
+ * SaveYourselfs to SmSaveBoth here, because we want to give
+ * the user a chance to save open files before quitting.
+ *
+ * (* It would be nice if we could do something useful when the
+ * session manager sends a SaveYourself with shutdown True and
+ * SmInteractStyleNone. But we can't, so we just pretend it didn't
+ * even tell us it was shutting down. The docs for ::quit mention
+ * that it might not always be preceded by ::quit_requested.)
+ */
+
+ /* As an optimization, we don't actually request interaction and
+ * emit ::quit_requested if the application isn't listening to the
+ * signal.
+ */
+ wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE);
+
+ xsmp->need_save_state = (save_type != SmSaveGlobal);
+ xsmp->need_quit_requested = (shutdown && wants_quit_requested &&
+ interact_style != SmInteractStyleNone);
+ xsmp->interact_errors = (interact_style == SmInteractStyleErrors);
+
+ xsmp->shutting_down = shutdown;
+
+ do_save_yourself (xsmp);
+}
+
+static void
+do_save_yourself (EggSMClientXSMP *xsmp)
+{
+ if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* The SM cancelled a previous SaveYourself, but we haven't yet
+ * had a chance to tell the application, so we can't start
+ * processing this SaveYourself yet.
+ */
+ xsmp->waiting_to_save_myself = TRUE;
+ update_pending_events (xsmp);
+ return;
+ }
+
+ if (xsmp->need_quit_requested)
+ {
+ xsmp->state = XSMP_STATE_INTERACT_REQUEST;
+
+ g_debug ("Sending InteractRequest(%s)",
+ xsmp->interact_errors ? "Error" : "Normal");
+ SmcInteractRequest (xsmp->connection,
+ xsmp->interact_errors ? SmDialogError : SmDialogNormal,
+ xsmp_interact,
+ xsmp);
+ return;
+ }
+
+ if (xsmp->need_save_state)
+ {
+ save_state (xsmp);
+
+ /* Though unlikely, the client could have been disconnected
+ * while the application was saving its state.
+ */
+ if (!xsmp->connection)
+ return;
+ }
+
+ g_debug ("Sending SaveYourselfDone(True)");
+ SmcSaveYourselfDone (xsmp->connection, True);
+
+ /* The client state diagram in the XSMP spec says that after a
+ * non-shutdown SaveYourself, we go directly back to "idle". But
+ * everything else in both the XSMP spec and the libSM docs
+ * disagrees.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
+}
+
+static void
+save_state (EggSMClientXSMP *xsmp)
+{
+ GKeyFile *state_file;
+ char *state_file_path, *data;
+ EggDesktopFile *desktop_file;
+ GPtrArray *restart;
+ int offset, fd;
+
+ /* We set xsmp->state before emitting save_state, but our caller is
+ * responsible for setting it back afterward.
+ */
+ xsmp->state = XSMP_STATE_SAVE_YOURSELF;
+
+ state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
+ if (!state_file)
+ {
+ restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+ delete_properties (xsmp, SmDiscardCommand, NULL);
+ return;
+ }
+
+ desktop_file = egg_get_desktop_file ();
+ if (desktop_file)
+ {
+ GKeyFile *merged_file;
+
+ merged_file = g_key_file_new ();
+ if (g_key_file_load_from_file (merged_file,
+ egg_desktop_file_get_source (desktop_file),
+ G_KEY_FILE_KEEP_COMMENTS |
+ G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
+ {
+ int g, k, i;
+ char **groups, **keys, *value, *exec;
+
+ groups = g_key_file_get_groups (state_file, NULL);
+ for (g = 0; groups[g]; g++)
+ {
+ keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL);
+ for (k = 0; keys[k]; k++)
+ {
+ value = g_key_file_get_value (state_file, groups[g],
+ keys[k], NULL);
+ if (value)
+ {
+ g_key_file_set_value (merged_file, groups[g],
+ keys[k], value);
+ g_free (value);
+ }
+ }
+ g_strfreev (keys);
+ }
+ g_strfreev (groups);
+
+ g_key_file_free (state_file);
+ state_file = merged_file;
+
+ /* Update Exec key using "--sm-client-state-file %k" */
+ restart = generate_command (xsmp->restart_command,
+ NULL, "%k");
+ for (i = 0; i < restart->len; i++)
+ restart->pdata[i] = g_shell_quote (restart->pdata[i]);
+ g_ptr_array_add (restart, NULL);
+ exec = g_strjoinv (" ", (char **)restart->pdata);
+ g_strfreev ((char **)restart->pdata);
+ g_ptr_array_free (restart, FALSE);
+
+ g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
+ EGG_DESKTOP_FILE_KEY_EXEC,
+ exec);
+ g_free (exec);
+
+ }
+ }
+
+ /* Now write state_file to disk. (We can't use mktemp(), because
+ * that requires the filename to end with "XXXXXX", and we want
+ * it to end with ".desktop".)
+ */
+
+ data = g_key_file_to_data (state_file, NULL, NULL);
+ g_key_file_free (state_file);
+
+ offset = 0;
+ while (1)
+ {
+ state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
+ g_get_user_config_dir (),
+ G_DIR_SEPARATOR, G_DIR_SEPARATOR,
+ g_get_prgname (),
+ (long)time (NULL) + offset,
+ desktop_file ? "desktop" : "state");
+
+ fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+ if (fd == -1)
+ {
+ if (errno == EEXIST)
+ {
+ offset++;
+ g_free (state_file_path);
+ continue;
+ }
+ else if (errno == ENOTDIR || errno == ENOENT)
+ {
+ char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
+
+ *sep = '\0';
+ if (g_mkdir_with_parents (state_file_path, 0755) != 0)
+ {
+ g_warning ("Could not create directory '%s'",
+ state_file_path);
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ continue;
+ }
+
+ g_warning ("Could not create file '%s': %s",
+ state_file_path, g_strerror (errno));
+ g_free (state_file_path);
+ state_file_path = NULL;
+ break;
+ }
+
+ close (fd);
+ g_file_set_contents (state_file_path, data, -1, NULL);
+ break;
+ }
+ g_free (data);
+
+ restart = generate_command (xsmp->restart_command, xsmp->client_id,
+ state_file_path);
+ set_properties (xsmp,
+ ptrarray_prop (SmRestartCommand, restart),
+ NULL);
+ g_ptr_array_free (restart, TRUE);
+
+ if (state_file_path)
+ {
+ set_properties (xsmp,
+ array_prop (SmDiscardCommand,
+ "/bin/rm", "-rf", state_file_path,
+ NULL),
+ NULL);
+ g_free (state_file_path);
+ }
+}
+
+static void
+xsmp_interact (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Interact message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
+ {
+ fix_broken_state (xsmp, "Interact", TRUE, TRUE);
+ return;
+ }
+
+ xsmp->state = XSMP_STATE_INTERACT;
+ egg_sm_client_quit_requested (client);
+}
+
+static void
+xsmp_die (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received Die message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ sm_client_xsmp_disconnect (xsmp);
+ egg_sm_client_quit (client);
+}
+
+static void
+xsmp_save_complete (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+
+ g_debug ("Received SaveComplete message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ xsmp->state = XSMP_STATE_IDLE;
+ else
+ fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
+}
+
+static void
+xsmp_shutdown_cancelled (SmcConn smc_conn,
+ SmPointer client_data)
+{
+ EggSMClientXSMP *xsmp = client_data;
+ EggSMClient *client = client_data;
+
+ g_debug ("Received ShutdownCancelled message in state %s",
+ EGG_SM_CLIENT_XSMP_STATE (xsmp));
+
+ xsmp->shutting_down = FALSE;
+
+ if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
+ {
+ /* We've finished interacting and now the SM has agreed to
+ * cancel the shutdown.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ egg_sm_client_quit_cancelled (client);
+ }
+ else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
+ {
+ /* Hm... ok, so we got a shutdown SaveYourself, which got
+ * cancelled, but the application was still interacting, so we
+ * didn't tell it yet, and then *another* SaveYourself arrived,
+ * which we must still be waiting to tell the app about, except
+ * that now that SaveYourself has been cancelled too! Dizzy yet?
+ */
+ xsmp->waiting_to_save_myself = FALSE;
+ update_pending_events (xsmp);
+ }
+ else
+ {
+ g_debug ("Sending SaveYourselfDone(False)");
+ SmcSaveYourselfDone (xsmp->connection, False);
+
+ if (xsmp->state == XSMP_STATE_INTERACT)
+ {
+ /* The application is currently interacting, so we can't
+ * tell it about the cancellation yet; we will wait until
+ * after it calls egg_sm_client_will_quit().
+ */
+ xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
+ }
+ else
+ {
+ /* The shutdown was cancelled before the application got a
+ * chance to interact.
+ */
+ xsmp->state = XSMP_STATE_IDLE;
+ }
+ }
+}
+
+/* Utilities */
+
+/* Create a restart/clone/Exec command based on @restart_command.
+ * If @client_id is non-%NULL, add "--sm-client-id @client_id".
+ * If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
+ *
+ * None of the input strings are g_strdup()ed; the caller must keep
+ * them around until it is done with the returned GPtrArray, and must
+ * then free the array, but not its contents.
+ */
+static GPtrArray *
+generate_command (char **restart_command, const char *client_id,
+ const char *state_file)
+{
+ GPtrArray *cmd;
+ int i;
+
+ cmd = g_ptr_array_new ();
+ g_ptr_array_add (cmd, restart_command[0]);
+
+ if (client_id)
+ {
+ g_ptr_array_add (cmd, "--sm-client-id");
+ g_ptr_array_add (cmd, (char *)client_id);
+ }
+
+ if (state_file)
+ {
+ g_ptr_array_add (cmd, "--sm-client-state-file");
+ g_ptr_array_add (cmd, (char *)state_file);
+ }
+
+ for (i = 1; restart_command[i]; i++)
+ g_ptr_array_add (cmd, restart_command[i]);
+
+ return cmd;
+}
+
+/* Takes a NULL-terminated list of SmProp * values, created by
+ * array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
+ * frees them.
+ */
+static void
+set_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ SmProp *prop;
+ va_list ap;
+ int i;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, SmProp *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ if (xsmp->connection)
+ {
+ SmcSetProperties (xsmp->connection, props->len,
+ (SmProp **)props->pdata);
+ }
+
+ for (i = 0; i < props->len; i++)
+ {
+ prop = props->pdata[i];
+ g_free (prop->vals);
+ g_free (prop);
+ }
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes a NULL-terminated list of property names and deletes them. */
+static void
+delete_properties (EggSMClientXSMP *xsmp, ...)
+{
+ GPtrArray *props;
+ char *prop;
+ va_list ap;
+
+ if (!xsmp->connection)
+ return;
+
+ props = g_ptr_array_new ();
+
+ va_start (ap, xsmp);
+ while ((prop = va_arg (ap, char *)))
+ g_ptr_array_add (props, prop);
+ va_end (ap);
+
+ SmcDeleteProperties (xsmp->connection, props->len,
+ (char **)props->pdata);
+
+ g_ptr_array_free (props, TRUE);
+}
+
+/* Takes an array of strings and creates a LISTofARRAY8 property. The
+ * strings are neither dupped nor freed; they need to remain valid
+ * until you're done with the SmProp.
+ */
+static SmProp *
+array_prop (const char *name, ...)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ char *value;
+ va_list ap;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ va_start (ap, name);
+ while ((value = va_arg (ap, char *)))
+ {
+ pv.length = strlen (value);
+ pv.value = value;
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
+ * The array contents are neither dupped nor freed; they need to
+ * remain valid until you're done with the SmProp.
+ */
+static SmProp *
+ptrarray_prop (const char *name, GPtrArray *values)
+{
+ SmProp *prop;
+ SmPropValue pv;
+ GArray *vals;
+ int i;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmLISTofARRAY8;
+
+ vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
+
+ for (i = 0; i < values->len; i++)
+ {
+ pv.length = strlen (values->pdata[i]);
+ pv.value = values->pdata[i];
+ g_array_append_val (vals, pv);
+ }
+
+ prop->num_vals = vals->len;
+ prop->vals = (SmPropValue *)vals->data;
+
+ g_array_free (vals, FALSE);
+
+ return prop;
+}
+
+/* Takes a string and creates an ARRAY8 property. The string is
+ * neither dupped nor freed; it needs to remain valid until you're
+ * done with the SmProp.
+ */
+static SmProp *
+string_prop (const char *name, const char *value)
+{
+ SmProp *prop;
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmARRAY8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 1);
+
+ prop->vals[0].length = strlen (value);
+ prop->vals[0].value = (char *)value;
+
+ return prop;
+}
+
+/* Takes a char and creates a CARD8 property. */
+static SmProp *
+card8_prop (const char *name, unsigned char value)
+{
+ SmProp *prop;
+ char *card8val;
+
+ /* To avoid having to allocate and free prop->vals[0], we cheat and
+ * make vals a 2-element-long array and then use the second element
+ * to store value.
+ */
+
+ prop = g_new (SmProp, 1);
+ prop->name = (char *)name;
+ prop->type = SmCARD8;
+
+ prop->num_vals = 1;
+ prop->vals = g_new (SmPropValue, 2);
+ card8val = (char *)(&prop->vals[1]);
+ card8val[0] = value;
+
+ prop->vals[0].length = 1;
+ prop->vals[0].value = card8val;
+
+ return prop;
+}
+
+/* ICE code. This makes no effort to play nice with anyone else trying
+ * to use libICE. Fortunately, no one uses libICE for anything other
+ * than SM. (DCOP uses ICE, but it has its own private copy of
+ * libICE.)
+ *
+ * When this moves to gtk, it will need to be cleverer, to avoid
+ * tripping over old apps that use GnomeClient or that use libSM
+ * directly.
+ */
+
+#include <X11/ICE/ICElib.h>
+#include <fcntl.h>
+
+static void ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values);
+static void ice_io_error_handler (IceConn ice_conn);
+static void ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data);
+
+static void
+ice_init (void)
+{
+ IceSetIOErrorHandler (ice_io_error_handler);
+ IceSetErrorHandler (ice_error_handler);
+ IceAddConnectionWatch (ice_connection_watch, NULL);
+}
+
+static gboolean
+process_ice_messages (IceConn ice_conn)
+{
+ IceProcessMessagesStatus status;
+
+ gdk_threads_enter ();
+ status = IceProcessMessages (ice_conn, NULL, NULL);
+ gdk_threads_leave ();
+
+ switch (status)
+ {
+ case IceProcessMessagesSuccess:
+ return TRUE;
+
+ case IceProcessMessagesIOError:
+ sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
+ return FALSE;
+
+ case IceProcessMessagesConnectionClosed:
+ return FALSE;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+ice_iochannel_watch (GIOChannel *channel,
+ GIOCondition condition,
+ gpointer client_data)
+{
+ return process_ice_messages (client_data);
+}
+
+static void
+ice_connection_watch (IceConn ice_conn,
+ IcePointer client_data,
+ Bool opening,
+ IcePointer *watch_data)
+{
+ guint watch_id;
+
+ if (opening)
+ {
+ GIOChannel *channel;
+ int fd = IceConnectionNumber (ice_conn);
+
+ fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
+ channel = g_io_channel_unix_new (fd);
+ watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
+ ice_iochannel_watch, ice_conn);
+ g_io_channel_unref (channel);
+
+ *watch_data = GUINT_TO_POINTER (watch_id);
+ }
+ else
+ {
+ watch_id = GPOINTER_TO_UINT (*watch_data);
+ g_source_remove (watch_id);
+ }
+}
+
+static void
+ice_error_handler (IceConn ice_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ IcePointer values)
+{
+ /* Do nothing */
+}
+
+static void
+ice_io_error_handler (IceConn ice_conn)
+{
+ /* Do nothing */
+}
+
+static void
+smc_error_handler (SmcConn smc_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence,
+ int error_class,
+ int severity,
+ SmPointer values)
+{
+ /* Do nothing */
+}
diff --git a/src/sugar/eggsmclient.c b/src/sugar/eggsmclient.c
new file mode 100644
index 0000000..86036f9
--- /dev/null
+++ b/src/sugar/eggsmclient.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#include "eggsmclient.h"
+#include "eggsmclient-private.h"
+
+static void egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data);
+
+enum {
+ SAVE_STATE,
+ QUIT_REQUESTED,
+ QUIT_CANCELLED,
+ QUIT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _EggSMClientPrivate {
+ GKeyFile *state_file;
+};
+
+#define EGG_SM_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_SM_CLIENT, EggSMClientPrivate))
+
+G_DEFINE_TYPE (EggSMClient, egg_sm_client, G_TYPE_OBJECT)
+
+static void
+egg_sm_client_init (EggSMClient *client)
+{
+}
+
+static void
+egg_sm_client_class_init (EggSMClientClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (EggSMClientPrivate));
+
+ /**
+ * EggSMClient::save_state:
+ * @client: the client
+ * @state_file: a #GKeyFile to save state information into
+ *
+ * Emitted when the session manager has requested that the
+ * application save information about its current state. The
+ * application should save its state into @state_file, and then the
+ * session manager may then restart the application in a future
+ * session and tell it to initialize itself from that state.
+ *
+ * You should not save any data into @state_file's "start group"
+ * (ie, the %NULL group). Instead, applications should save their
+ * data into groups with names that start with the application name,
+ * and libraries that connect to this signal should save their data
+ * into groups with names that start with the library name.
+ *
+ * Alternatively, rather than (or in addition to) using @state_file,
+ * the application can save its state by calling
+ * egg_sm_client_set_restart_command() during the processing of this
+ * signal (eg, to include a list of files to open).
+ **/
+ signals[SAVE_STATE] =
+ g_signal_new ("save_state",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, save_state),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+
+ /**
+ * EggSMClient::quit_requested:
+ * @client: the client
+ *
+ * Emitted when the session manager requests that the application
+ * exit (generally because the user is logging out). The application
+ * should decide whether or not it is willing to quit (perhaps after
+ * asking the user what to do with documents that have unsaved
+ * changes) and then call egg_sm_client_will_quit(), passing %TRUE
+ * or %FALSE to give its answer to the session manager. (It does not
+ * need to give an answer before returning from the signal handler;
+ * it can interact with the user asynchronously and then give its
+ * answer later on.) If the application does not connect to this
+ * signal, then #EggSMClient will automatically return %TRUE on its
+ * behalf.
+ *
+ * The application should not save its session state as part of
+ * handling this signal; if the user has requested that the session
+ * be saved when logging out, then ::save_state will be emitted
+ * separately.
+ *
+ * If the application agrees to quit, it should then wait for either
+ * the ::quit_cancelled or ::quit signals to be emitted.
+ **/
+ signals[QUIT_REQUESTED] =
+ g_signal_new ("quit_requested",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_requested),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit_cancelled:
+ * @client: the client
+ *
+ * Emitted when the session manager decides to cancel a logout after
+ * the application has already agreed to quit. After receiving this
+ * signal, the application can go back to what it was doing before
+ * receiving the ::quit_requested signal.
+ **/
+ signals[QUIT_CANCELLED] =
+ g_signal_new ("quit_cancelled",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit_cancelled),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+
+ /**
+ * EggSMClient::quit:
+ * @client: the client
+ *
+ * Emitted when the session manager wants the application to quit
+ * (generally because the user is logging out). The application
+ * should exit as soon as possible after receiving this signal; if
+ * it does not, the session manager may choose to forcibly kill it.
+ *
+ * Normally a GUI application would only be sent a ::quit if it
+ * agreed to quit in response to a ::quit_requested signal. However,
+ * this is not guaranteed; in some situations the session manager
+ * may decide to end the session without giving applications a
+ * chance to object.
+ **/
+ signals[QUIT] =
+ g_signal_new ("quit",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EggSMClientClass, quit),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+static gboolean sm_client_disable = FALSE;
+static char *sm_client_state_file = NULL;
+static char *sm_client_id = NULL;
+
+static GOptionEntry entries[] = {
+ { "sm-client-disable", 0, 0,
+ G_OPTION_ARG_NONE, &sm_client_disable,
+ N_("Disable connection to session manager"), NULL },
+ { "sm-client-state-file", 0, 0,
+ G_OPTION_ARG_STRING, &sm_client_state_file,
+ N_("Specify file containing saved configuration"), N_("FILE") },
+ { "sm-client-id", 0, 0,
+ G_OPTION_ARG_STRING, &sm_client_id,
+ N_("Specify session management ID"), N_("ID") },
+ { NULL }
+};
+
+/**
+ * egg_sm_client_is_resumed:
+ * @client: the client
+ *
+ * Checks whether or not the current session has been resumed from
+ * a previous saved session. If so, the application should call
+ * egg_sm_client_get_state_file() and restore its state from the
+ * returned #GKeyFile.
+ *
+ * Return value: %TRUE if the session has been resumed
+ **/
+gboolean
+egg_sm_client_is_resumed (EggSMClient *client)
+{
+ return sm_client_state_file != NULL;
+}
+
+/**
+ * egg_sm_client_get_state_file:
+ * @client: the client
+ *
+ * If the application was resumed by the session manager, this will
+ * return the #GKeyFile containing its state from the previous
+ * session.
+ *
+ * Note that other libraries and #EggSMClient itself may also store
+ * state in the key file, so if you call egg_sm_client_get_groups(),
+ * on it, the return value will likely include groups that you did not
+ * put there yourself. (It is also not guaranteed that the first
+ * group created by the application will still be the "start group"
+ * when it is resumed.)
+ *
+ * Return value: the #GKeyFile containing the application's earlier
+ * state, or %NULL on error. You should not free this key file; it
+ * is owned by @client.
+ **/
+GKeyFile *
+egg_sm_client_get_state_file (EggSMClient *client)
+{
+ EggSMClientPrivate *priv = EGG_SM_CLIENT_GET_PRIVATE (client);
+ char *state_file_path;
+ GError *err = NULL;
+
+ if (!sm_client_state_file)
+ return NULL;
+ if (priv->state_file)
+ return priv->state_file;
+
+ if (!strncmp (sm_client_state_file, "file://", 7))
+ state_file_path = g_filename_from_uri (sm_client_state_file, NULL, NULL);
+ else
+ state_file_path = g_strdup (sm_client_state_file);
+
+ priv->state_file = g_key_file_new ();
+ if (!g_key_file_load_from_file (priv->state_file, state_file_path, 0, &err))
+ {
+ g_warning ("Could not load SM state file '%s': %s",
+ sm_client_state_file, err->message);
+ g_clear_error (&err);
+ g_key_file_free (priv->state_file);
+ priv->state_file = NULL;
+ }
+
+ g_free (state_file_path);
+ return priv->state_file;
+}
+
+/**
+ * egg_sm_client_set_restart_command:
+ * @client: the client
+ * @argc: the length of @argv
+ * @argv: argument vector
+ *
+ * Sets the command used to restart @client if it does not have a
+ * .desktop file that can be used to find its restart command.
+ *
+ * This can also be used when handling the ::save_state signal, to
+ * save the current state via an updated command line. (Eg, providing
+ * a list of filenames to open when the application is resumed.)
+ **/
+void
+egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command)
+ EGG_SM_CLIENT_GET_CLASS (client)->set_restart_command (client, argc, argv);
+}
+
+/**
+ * egg_sm_client_will_quit:
+ * @client: the client
+ * @will_quit: whether or not the application is willing to quit
+ *
+ * This MUST be called in response to the ::quit_requested signal, to
+ * indicate whether or not the application is willing to quit. The
+ * application may call it either directly from the signal handler, or
+ * at some later point (eg, after asynchronously interacting with the
+ * user).
+ *
+ * If the application does not connect to ::quit_requested,
+ * #EggSMClient will call this method on its behalf (passing %TRUE
+ * for @will_quit).
+ *
+ * After calling this method, the application should wait to receive
+ * either ::quit_cancelled or ::quit.
+ **/
+void
+egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit)
+{
+ g_return_if_fail (EGG_IS_SM_CLIENT (client));
+
+ if (EGG_SM_CLIENT_GET_CLASS (client)->will_quit)
+ EGG_SM_CLIENT_GET_CLASS (client)->will_quit (client, will_quit);
+}
+
+/* Signal-emitting callbacks from platform-specific code */
+
+GKeyFile *
+egg_sm_client_save_state (EggSMClient *client)
+{
+ GKeyFile *state_file;
+ char *group;
+
+ state_file = g_key_file_new ();
+
+ g_debug ("Emitting save_state");
+ g_signal_emit (client, signals[SAVE_STATE], 0, state_file);
+ g_debug ("Done emitting save_state");
+
+ group = g_key_file_get_start_group (state_file);
+ if (group)
+ {
+ g_free (group);
+ return state_file;
+ }
+ else
+ {
+ g_key_file_free (state_file);
+ return NULL;
+ }
+}
+
+void
+egg_sm_client_quit_requested (EggSMClient *client)
+{
+ if (!g_signal_has_handler_pending (client, signals[QUIT_REQUESTED], 0, FALSE))
+ {
+ g_debug ("Not emitting quit_requested because no one is listening");
+ egg_sm_client_will_quit (client, TRUE);
+ return;
+ }
+
+ g_debug ("Emitting quit_requested");
+ g_signal_emit (client, signals[QUIT_REQUESTED], 0);
+ g_debug ("Done emitting quit_requested");
+}
+
+void
+egg_sm_client_quit_cancelled (EggSMClient *client)
+{
+ g_debug ("Emitting quit_cancelled");
+ g_signal_emit (client, signals[QUIT_CANCELLED], 0);
+ g_debug ("Done emitting quit_cancelled");
+}
+
+void
+egg_sm_client_quit (EggSMClient *client)
+{
+ g_debug ("Emitting quit");
+ g_signal_emit (client, signals[QUIT], 0);
+ g_debug ("Done emitting quit");
+
+ /* FIXME: should we just call gtk_main_quit() here? */
+}
+
+void
+egg_sm_client_startup (EggSMClient *client)
+{
+ if (EGG_SM_CLIENT_GET_CLASS (client)->startup)
+ EGG_SM_CLIENT_GET_CLASS (client)->startup (client, sm_client_id);
+}
+
+static void
+egg_sm_client_debug_handler (const char *log_domain,
+ GLogLevelFlags log_level,
+ const char *message,
+ gpointer user_data)
+{
+ static int debug = -1;
+
+ if (debug < 0)
+ debug = (g_getenv ("EGG_SM_CLIENT_DEBUG") != NULL);
+
+ if (debug)
+ g_log_default_handler (log_domain, log_level, message, NULL);
+}
diff --git a/src/sugar/eggsmclient.h b/src/sugar/eggsmclient.h
new file mode 100644
index 0000000..52d85de
--- /dev/null
+++ b/src/sugar/eggsmclient.h
@@ -0,0 +1,112 @@
+/* eggsmclient.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EGG_SM_CLIENT_H__
+#define __EGG_SM_CLIENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define EGG_TYPE_SM_CLIENT (egg_sm_client_get_type ())
+#define EGG_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EGG_TYPE_SM_CLIENT, EggSMClient))
+#define EGG_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+#define EGG_IS_SM_CLIENT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EGG_TYPE_SM_CLIENT))
+#define EGG_IS_SM_CLIENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EGG_TYPE_SM_CLIENT))
+#define EGG_SM_CLIENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EGG_TYPE_SM_CLIENT, EggSMClientClass))
+
+typedef struct _EggSMClient EggSMClient;
+typedef struct _EggSMClientClass EggSMClientClass;
+typedef struct _EggSMClientPrivate EggSMClientPrivate;
+
+typedef enum {
+ EGG_SM_CLIENT_END_SESSION_DEFAULT,
+ EGG_SM_CLIENT_LOGOUT,
+ EGG_SM_CLIENT_REBOOT,
+ EGG_SM_CLIENT_SHUTDOWN
+} EggSMClientEndStyle;
+
+typedef enum {
+ EGG_SM_CLIENT_MODE_DISABLED,
+ EGG_SM_CLIENT_MODE_NO_RESTART,
+ EGG_SM_CLIENT_MODE_NORMAL
+} EggSMClientMode;
+
+struct _EggSMClient
+{
+ GObject parent;
+
+};
+
+struct _EggSMClientClass
+{
+ GObjectClass parent_class;
+
+ /* signals */
+ void (*save_state) (EggSMClient *client,
+ GKeyFile *state_file);
+
+ void (*quit_requested) (EggSMClient *client);
+ void (*quit_cancelled) (EggSMClient *client);
+ void (*quit) (EggSMClient *client);
+
+ /* virtual methods */
+ void (*startup) (EggSMClient *client,
+ const char *client_id);
+ void (*set_restart_command) (EggSMClient *client,
+ int argc,
+ const char **argv);
+ void (*will_quit) (EggSMClient *client,
+ gboolean will_quit);
+ gboolean (*end_session) (EggSMClient *client,
+ EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+ /* Padding for future expansion */
+ void (*_egg_reserved1) (void);
+ void (*_egg_reserved2) (void);
+ void (*_egg_reserved3) (void);
+ void (*_egg_reserved4) (void);
+};
+
+GType egg_sm_client_get_type (void) G_GNUC_CONST;
+
+/* Resuming a saved session */
+gboolean egg_sm_client_is_resumed (EggSMClient *client);
+GKeyFile *egg_sm_client_get_state_file (EggSMClient *client);
+
+/* Alternate means of saving state */
+void egg_sm_client_set_restart_command (EggSMClient *client,
+ int argc,
+ const char **argv);
+
+/* Handling "quit_requested" signal */
+void egg_sm_client_will_quit (EggSMClient *client,
+ gboolean will_quit);
+
+void egg_sm_client_startup (EggSMClient *client);
+
+/* Initiate a logout/reboot/shutdown */
+gboolean egg_sm_client_end_session (EggSMClientEndStyle style,
+ gboolean request_confirmation);
+
+G_END_DECLS
+
+
+#endif /* __EGG_SM_CLIENT_H__ */
diff --git a/src/sugar/session.c b/src/sugar/session.c
new file mode 100644
index 0000000..37e3587
--- /dev/null
+++ b/src/sugar/session.c
@@ -0,0 +1,550 @@
+/* session.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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 <string.h>
+
+#include "session.h"
+#include "app.h"
+#include "xsmp.h"
+
+GsmSession *global_session;
+
+static void initiate_shutdown (GsmSession *session);
+
+static void session_shutdown (GsmSession *session);
+
+static void client_saved_state (GsmClient *client,
+ gpointer data);
+static void client_request_phase2 (GsmClient *client,
+ gpointer data);
+static void client_request_interaction (GsmClient *client,
+ gpointer data);
+static void client_interaction_done (GsmClient *client,
+ gboolean cancel_shutdown,
+ gpointer data);
+static void client_save_yourself_done (GsmClient *client,
+ gpointer data);
+static void client_disconnected (GsmClient *client,
+ gpointer data);
+
+struct _GsmSession {
+ GObject parent;
+
+ char *name;
+
+ /* Current status */
+ GsmSessionPhase phase;
+ guint timeout;
+ GSList *pending_apps;
+
+ /* SM clients */
+ GSList *clients;
+
+ /* When shutdown starts, all clients are put into shutdown_clients.
+ * If they request phase2, they are moved from shutdown_clients to
+ * phase2_clients. If they request interaction, they are appended
+ * to interact_clients (the first client in interact_clients is
+ * the one currently interacting). If they report that they're done,
+ * they're removed from shutdown_clients/phase2_clients.
+ *
+ * Once shutdown_clients is empty, phase2 starts. Once phase2_clients
+ * is empty, shutdown is complete.
+ */
+ GSList *shutdown_clients;
+ GSList *interact_clients;
+ GSList *phase2_clients;
+
+ /* List of clients which were disconnected due to disabled condition
+ * and shouldn't be automatically restarted */
+ GSList *condition_clients;
+};
+
+struct _GsmSessionClass
+{
+ GObjectClass parent_class;
+
+ void (* shutdown_completed) (GsmSession *client);
+};
+
+enum {
+ SHUTDOWN_COMPLETED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (GsmSession, gsm_session, G_TYPE_OBJECT)
+
+#define GSM_SESSION_PHASE_TIMEOUT 10 /* seconds */
+
+void
+gsm_session_init (GsmSession *session)
+{
+ session->name = NULL;
+ session->clients = NULL;
+ session->condition_clients = NULL;
+}
+
+static void
+gsm_session_class_init (GsmSessionClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ signals[SHUTDOWN_COMPLETED] =
+ g_signal_new ("shutdown_completed",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (GsmSessionClass, shutdown_completed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+
+/**
+ * gsm_session_set_name:
+ * @session: session instance
+ * @name: name of the session
+ *
+ * Sets the name of a running session.
+ **/
+void
+gsm_session_set_name (GsmSession *session, const char *name)
+{
+ if (session->name)
+ g_free (session->name);
+
+ session->name = g_strdup (name);
+}
+
+static void start_phase (GsmSession *session);
+
+static void
+end_phase (GsmSession *session)
+{
+ g_slist_free (session->pending_apps);
+ session->pending_apps = NULL;
+
+ g_debug ("ending phase %d\n", session->phase);
+
+ session->phase++;
+
+ if (session->phase < GSM_SESSION_PHASE_RUNNING)
+ start_phase (session);
+}
+
+static void
+app_condition_changed (GsmApp *app, gboolean condition, gpointer data)
+{
+ GsmSession *session;
+ GsmClient *client = NULL;
+ GSList *cl = NULL;
+
+ g_return_if_fail (data != NULL);
+
+ session = (GsmSession *) data;
+
+ /* Check for an existing session client for this app */
+ for (cl = session->clients; cl; cl = cl->next)
+ {
+ GsmClient *c = GSM_CLIENT (cl->data);
+
+ if (!strcmp (app->client_id, gsm_client_get_client_id (c)))
+ client = c;
+ }
+
+ if (condition)
+ {
+ GError *error = NULL;
+
+ if (app->pid <= 0 && client == NULL)
+ gsm_app_launch (app, &error);
+
+ if (error != NULL)
+ {
+ g_warning ("Not able to launch autostart app from its condition: %s",
+ error->message);
+
+ g_error_free (error);
+ }
+ }
+ else
+ {
+ /* Kill client in case condition if false and make sure it won't
+ * be automatically restarted by adding the client to
+ * condition_clients */
+ session->condition_clients =
+ g_slist_prepend (session->condition_clients, client);
+ gsm_client_die (client);
+ app->pid = -1;
+ }
+}
+
+static void
+app_registered (GsmApp *app, gpointer data)
+{
+ GsmSession *session = data;
+
+ session->pending_apps = g_slist_remove (session->pending_apps, app);
+ g_signal_handlers_disconnect_by_func (app, app_registered, session);
+
+ if (!session->pending_apps)
+ {
+ if (session->timeout > 0)
+ {
+ g_source_remove (session->timeout);
+ session->timeout = 0;
+ }
+
+ end_phase (session);
+ }
+}
+
+static gboolean
+phase_timeout (gpointer data)
+{
+ GsmSession *session = data;
+ GSList *a;
+
+ session->timeout = 0;
+
+ for (a = session->pending_apps; a; a = a->next)
+ {
+ g_warning ("Application '%s' failed to register before timeout",
+ gsm_app_get_basename (a->data));
+ g_signal_handlers_disconnect_by_func (a->data, app_registered, session);
+
+ /* FIXME: what if the app was filling in a required slot? */
+ }
+
+ end_phase (session);
+ return FALSE;
+}
+
+static void
+start_phase (GsmSession *session)
+{
+ GsmApp *app;
+ GSList *a;
+ GError *err = NULL;
+
+ g_debug ("starting phase %d\n", session->phase);
+
+ g_slist_free (session->pending_apps);
+ session->pending_apps = NULL;
+
+ if (session->pending_apps)
+ {
+ if (session->phase < GSM_SESSION_PHASE_APPLICATION)
+ {
+ session->timeout = g_timeout_add (GSM_SESSION_PHASE_TIMEOUT * 1000,
+ phase_timeout, session);
+ }
+ }
+ else
+ end_phase (session);
+}
+
+void
+gsm_session_start (GsmSession *session)
+{
+ session->phase = GSM_SESSION_PHASE_INITIALIZATION;
+
+ start_phase (session);
+}
+
+GsmSessionPhase
+gsm_session_get_phase (GsmSession *session)
+{
+ return session->phase;
+}
+
+char *
+gsm_session_register_client (GsmSession *session,
+ GsmClient *client,
+ const char *id)
+{
+ GSList *a;
+ char *client_id = NULL;
+
+ /* If we're shutting down, we don't accept any new session
+ clients. */
+ if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
+ return FALSE;
+
+ if (id == NULL)
+ client_id = gsm_xsmp_generate_client_id ();
+ else
+ {
+ for (a = session->clients; a; a = a->next)
+ {
+ GsmClient *client = GSM_CLIENT (a->data);
+
+ /* We can't have two clients with the same id. */
+ if (!strcmp (id, gsm_client_get_client_id (client)))
+ {
+ return NULL;
+ }
+ }
+
+ client_id = g_strdup (id);
+ }
+
+ g_debug ("Adding new client %s to session", id);
+
+ g_signal_connect (client, "saved_state",
+ G_CALLBACK (client_saved_state), session);
+ g_signal_connect (client, "request_phase2",
+ G_CALLBACK (client_request_phase2), session);
+ g_signal_connect (client, "request_interaction",
+ G_CALLBACK (client_request_interaction), session);
+ g_signal_connect (client, "interaction_done",
+ G_CALLBACK (client_interaction_done), session);
+ g_signal_connect (client, "save_yourself_done",
+ G_CALLBACK (client_save_yourself_done), session);
+ g_signal_connect (client, "disconnected",
+ G_CALLBACK (client_disconnected), session);
+
+ session->clients = g_slist_prepend (session->clients, client);
+
+ /* If it's a brand new client id, we just accept the client*/
+ if (id == NULL)
+ return client_id;
+
+ /* If we're starting up the session, try to match the new client
+ * with one pending apps for the current phase. If not, try to match
+ * with any of the autostarted apps. */
+ if (session->phase < GSM_SESSION_PHASE_APPLICATION)
+ a = session->pending_apps;
+
+ for (; a; a = a->next)
+ {
+ GsmApp *app = GSM_APP (a->data);
+
+ if (!strcmp (client_id, app->client_id))
+ {
+ gsm_app_registered (app);
+ return client_id;
+ }
+ }
+
+ g_free (client_id);
+
+ return NULL;
+}
+
+static void
+client_saved_state (GsmClient *client, gpointer data)
+{
+ /* FIXME */
+}
+
+void
+gsm_session_initiate_shutdown (GsmSession *session)
+{
+ gboolean logout_prompt;
+
+ if (session->phase == GSM_SESSION_PHASE_SHUTDOWN)
+ {
+ /* Already shutting down, nothing more to do */
+ return;
+ }
+
+ initiate_shutdown (session);
+}
+
+static void
+session_shutdown_phase2 (GsmSession *session)
+{
+ GSList *cl;
+
+ for (cl = session->phase2_clients; cl; cl = cl->next)
+ gsm_client_save_yourself_phase2 (cl->data);
+}
+
+static void
+session_cancel_shutdown (GsmSession *session)
+{
+ GSList *cl;
+
+ session->phase = GSM_SESSION_PHASE_RUNNING;
+
+ g_slist_free (session->shutdown_clients);
+ session->shutdown_clients = NULL;
+ g_slist_free (session->interact_clients);
+ session->interact_clients = NULL;
+ g_slist_free (session->phase2_clients);
+ session->phase2_clients = NULL;
+
+ for (cl = session->clients; cl; cl = cl->next)
+ gsm_client_shutdown_cancelled (cl->data);
+}
+
+static void
+initiate_shutdown (GsmSession *session)
+{
+ GSList *cl;
+
+ session->phase = GSM_SESSION_PHASE_SHUTDOWN;
+
+ if (session->clients == NULL)
+ session_shutdown (session);
+
+ for (cl = session->clients; cl; cl = cl->next)
+ {
+ GsmClient *client = GSM_CLIENT (cl->data);
+
+ session->shutdown_clients =
+ g_slist_prepend (session->shutdown_clients, client);
+
+ gsm_client_save_yourself (client, FALSE);
+ }
+}
+
+static void
+session_shutdown (GsmSession *session)
+{
+ GSList *cl;
+
+ /* FIXME: do this in reverse phase order */
+ for (cl = session->clients; cl; cl = cl->next)
+ gsm_client_die (cl->data);
+}
+
+static void
+client_request_phase2 (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+
+ /* Move the client from shutdown_clients to phase2_clients */
+
+ session->shutdown_clients =
+ g_slist_remove (session->shutdown_clients, client);
+ session->phase2_clients =
+ g_slist_prepend (session->phase2_clients, client);
+}
+
+static void
+client_request_interaction (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+
+ session->interact_clients =
+ g_slist_append (session->interact_clients, client);
+
+ if (!session->interact_clients->next)
+ gsm_client_interact (client);
+}
+
+static void
+client_interaction_done (GsmClient *client, gboolean cancel_shutdown,
+ gpointer data)
+{
+ GsmSession *session = data;
+
+ g_return_if_fail (session->interact_clients &&
+ (GsmClient *)session->interact_clients->data == client);
+
+ if (cancel_shutdown)
+ {
+ session_cancel_shutdown (session);
+ return;
+ }
+
+ /* Remove this client from interact_clients, and if there's another
+ * client waiting to interact, let it interact now.
+ */
+ session->interact_clients =
+ g_slist_remove (session->interact_clients, client);
+ if (session->interact_clients)
+ gsm_client_interact (session->interact_clients->data);
+}
+
+static void
+client_save_yourself_done (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+
+ session->shutdown_clients =
+ g_slist_remove (session->shutdown_clients, client);
+ session->interact_clients =
+ g_slist_remove (session->interact_clients, client);
+ session->phase2_clients =
+ g_slist_remove (session->phase2_clients, client);
+
+ if (session->phase == GSM_SESSION_PHASE_SHUTDOWN &&
+ !session->shutdown_clients)
+ {
+ if (session->phase2_clients)
+ session_shutdown_phase2 (session);
+ else
+ session_shutdown (session);
+ }
+}
+
+static void
+client_disconnected (GsmClient *client, gpointer data)
+{
+ GsmSession *session = data;
+ gboolean is_condition_client = FALSE;
+
+ session->clients =
+ g_slist_remove (session->clients, client);
+ session->shutdown_clients =
+ g_slist_remove (session->shutdown_clients, client);
+ session->interact_clients =
+ g_slist_remove (session->interact_clients, client);
+ session->phase2_clients =
+ g_slist_remove (session->phase2_clients, client);
+
+ if (g_slist_find (session->condition_clients, client))
+ {
+ session->condition_clients =
+ g_slist_remove (session->condition_clients, client);
+
+ is_condition_client = TRUE;
+ }
+
+ if (session->phase != GSM_SESSION_PHASE_SHUTDOWN &&
+ gsm_client_get_autorestart (client) &&
+ !is_condition_client)
+ {
+ GError *error = NULL;
+
+ gsm_client_restart (client, &error);
+
+ if (error)
+ {
+ g_warning ("Error on restarting session client: %s", error->message);
+ g_clear_error (&error);
+ }
+ }
+
+ g_object_unref (client);
+
+ g_signal_emit (session, signals[SHUTDOWN_COMPLETED], 0);
+}
+
+GsmSession *
+gsm_session_create_global (void)
+{
+ global_session = GSM_SESSION(g_object_new (GSM_TYPE_SESSION, NULL));
+ return global_session;
+}
diff --git a/src/sugar/session.h b/src/sugar/session.h
new file mode 100644
index 0000000..cc4376a
--- /dev/null
+++ b/src/sugar/session.h
@@ -0,0 +1,93 @@
+/* session.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifndef __GSM_SESSION_H__
+#define __GSM_SESSION_H__
+
+#include <glib.h>
+#include "client.h"
+
+G_BEGIN_DECLS
+
+#define GSM_TYPE_SESSION (gsm_session_get_type ())
+#define GSM_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSM_TYPE_SESSION, GsmSession))
+#define GSM_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSM_TYPE_SESSION, GsmSessionClass))
+#define GSM_IS_SESSION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSM_TYPE_SESSION))
+#define GSM_IS_SESSION_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSM_TYPE_SESSION))
+#define GSM_SESSION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSM_TYPE_SESSION, GsmSessionClass))
+
+typedef struct _GsmSession GsmSession;
+typedef struct _GsmSessionClass GsmSessionClass;
+extern GsmSession *global_session;
+
+typedef enum {
+ /* gsm's own startup/initialization phase */
+ GSM_SESSION_PHASE_STARTUP,
+
+ /* xrandr setup, gnome-settings-daemon, etc */
+ GSM_SESSION_PHASE_INITIALIZATION,
+
+ /* window/compositing managers */
+ GSM_SESSION_PHASE_WINDOW_MANAGER,
+
+ /* apps that will create _NET_WM_WINDOW_TYPE_PANEL windows */
+ GSM_SESSION_PHASE_PANEL,
+
+ /* apps that will create _NET_WM_WINDOW_TYPE_DESKTOP windows */
+ GSM_SESSION_PHASE_DESKTOP,
+
+ /* everything else */
+ GSM_SESSION_PHASE_APPLICATION,
+
+ /* done launching */
+ GSM_SESSION_PHASE_RUNNING,
+
+ /* shutting down */
+ GSM_SESSION_PHASE_SHUTDOWN
+} GsmSessionPhase;
+
+typedef enum {
+ GSM_SESSION_LOGOUT_TYPE_LOGOUT,
+ GSM_SESSION_LOGOUT_TYPE_SHUTDOWN
+} GsmSessionLogoutType;
+
+typedef enum {
+ GSM_SESSION_LOGOUT_MODE_NORMAL,
+ GSM_SESSION_LOGOUT_MODE_NO_CONFIRMATION,
+ GSM_SESSION_LOGOUT_MODE_FORCE
+} GsmSessionLogoutMode;
+
+void gsm_session_set_name (GsmSession *session,
+ const char *name);
+
+void gsm_session_start (GsmSession *session);
+
+GsmSessionPhase gsm_session_get_phase (GsmSession *session);
+
+void gsm_session_initiate_shutdown (GsmSession *session);
+
+char *gsm_session_register_client (GsmSession *session,
+ GsmClient *client,
+ const char *previous_id);
+
+GsmSession *gsm_session_create_global (void);
+
+G_END_DECLS
+
+#endif /* __GSM_SESSION_H__ */
diff --git a/src/sugar/session.py b/src/sugar/session.py
new file mode 100644
index 0000000..2125181
--- /dev/null
+++ b/src/sugar/session.py
@@ -0,0 +1,46 @@
+# Copyright (C) 2008, Red Hat, Inc.
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library 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
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+import os
+
+from sugar import _sugarext
+
+class XSMPClient(_sugarext.SMClientXSMP):
+ def __init__(self):
+ _sugarext.SMClientXSMP.__init__(self)
+
+class Session(object):
+ def __init__(self):
+ address = _sugarext.xsmp_init()
+ os.environ['SESSION_MANAGER'] = address
+ _sugarext.xsmp_run()
+
+ self.session = _sugarext.session_create_global()
+
+ def start(self):
+ self.session.start()
+ self.session.connect('shutdown_completed',
+ self.__shutdown_completed_cb)
+
+ def initiate_shutdown(self, ):
+ self.session.initiate_shutdown()
+
+ def shutdown_completed(self):
+ pass
+
+ def __shutdown_completed_cb(self, session):
+ self.shutdown_completed()
diff --git a/src/sugar/xsmp.c b/src/sugar/xsmp.c
new file mode 100644
index 0000000..d1260b2
--- /dev/null
+++ b/src/sugar/xsmp.c
@@ -0,0 +1,537 @@
+/* xsmp.c
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include "client-xsmp.h"
+//#include "gsm.h"
+//#include "util.h"
+#include "xsmp.h"
+
+#include <X11/ICE/ICElib.h>
+#include <X11/ICE/ICEutil.h>
+#include <X11/ICE/ICEconn.h>
+#include <X11/SM/SMlib.h>
+
+#ifdef HAVE_X11_XTRANS_XTRANS_H
+/* Get the proto for _IceTransNoListen */
+#define ICE_t
+#define TRANS_SERVER
+#include <X11/Xtrans/Xtrans.h>
+#undef ICE_t
+#undef TRANS_SERVER
+#endif /* HAVE_X11_XTRANS_XTRANS_H */
+
+static IceListenObj *xsmp_sockets;
+static int num_xsmp_sockets, num_local_xsmp_sockets;
+
+static gboolean update_iceauthority (gboolean adding);
+
+static gboolean accept_ice_connection (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data);
+static Status accept_xsmp_connection (SmsConn conn,
+ SmPointer manager_data,
+ unsigned long *mask_ret,
+ SmsCallbacks *callbacks_ret,
+ char **failure_reason_ret);
+
+static void ice_error_handler (IceConn conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence_num,
+ int error_class,
+ int severity,
+ IcePointer values);
+static void ice_io_error_handler (IceConn conn);
+static void sms_error_handler (SmsConn sms_conn,
+ Bool swap,
+ int offending_minor_opcode,
+ unsigned long offending_sequence_num,
+ int error_class,
+ int severity,
+ IcePointer values);
+/**
+ * gsm_xsmp_init:
+ *
+ * Initializes XSMP. Notably, it creates the XSMP listening socket and
+ * sets the SESSION_MANAGER environment variable to point to it.
+ **/
+char *
+gsm_xsmp_init (void)
+{
+ char error[256];
+ mode_t saved_umask;
+ char *network_id_list;
+ int i;
+
+ /* Set up sane error handlers */
+ IceSetErrorHandler (ice_error_handler);
+ IceSetIOErrorHandler (ice_io_error_handler);
+ SmsSetErrorHandler (sms_error_handler);
+
+ /* Initialize libSM; we pass NULL for hostBasedAuthProc to disable
+ * host-based authentication.
+ */
+ if (!SmsInitialize (PACKAGE, VERSION, accept_xsmp_connection,
+ NULL, NULL, sizeof (error), error))
+ g_error("Could not initialize libSM: %s", error);
+
+#ifdef HAVE_X11_XTRANS_XTRANS_H
+ /* By default, IceListenForConnections will open one socket for each
+ * transport type known to X. We don't want connections from remote
+ * hosts, so for security reasons it would be best if ICE didn't
+ * even open any non-local sockets. So we use an internal ICElib
+ * method to disable them here. Unfortunately, there is no way to
+ * ask X what transport types it knows about, so we're forced to
+ * guess.
+ */
+ _IceTransNoListen ("tcp");
+#endif
+
+ /* Create the XSMP socket. Older versions of IceListenForConnections
+ * have a bug which causes the umask to be set to 0 on certain types
+ * of failures. Probably not an issue on any modern systems, but
+ * we'll play it safe.
+ */
+ saved_umask = umask (0);
+ umask (saved_umask);
+ if (!IceListenForConnections (&num_xsmp_sockets, &xsmp_sockets,
+ sizeof (error), error))
+ g_error ("Could not create ICE listening socket: %s", error);
+ umask (saved_umask);
+
+ /* Find the local sockets in the returned socket list and move them
+ * to the start of the list.
+ */
+ for (i = num_local_xsmp_sockets = 0; i < num_xsmp_sockets; i++)
+ {
+ char *id = IceGetListenConnectionString (xsmp_sockets[i]);
+
+ if (!strncmp (id, "local/", sizeof ("local/") - 1) ||
+ !strncmp (id, "unix/", sizeof ("unix/") - 1))
+ {
+ if (i > num_local_xsmp_sockets)
+ {
+ IceListenObj tmp = xsmp_sockets[i];
+ xsmp_sockets[i] = xsmp_sockets[num_local_xsmp_sockets];
+ xsmp_sockets[num_local_xsmp_sockets] = tmp;
+ }
+ num_local_xsmp_sockets++;
+ }
+ free (id);
+ }
+
+ if (num_local_xsmp_sockets == 0)
+ g_error ("IceListenForConnections did not return a local listener!");
+
+#ifdef HAVE_X11_XTRANS_XTRANS_H
+ if (num_local_xsmp_sockets != num_xsmp_sockets)
+ {
+ /* Xtrans was apparently compiled with support for some
+ * non-local transport besides TCP (which we disabled above); we
+ * won't create IO watches on those extra sockets, so
+ * connections to them will never be noticed, but they're still
+ * there, which is inelegant.
+ *
+ * If the g_warning below is triggering for you and you want to
+ * stop it, the fix is to add additional _IceTransNoListen()
+ * calls above.
+ */
+ network_id_list =
+ IceComposeNetworkIdList (num_xsmp_sockets - num_local_xsmp_sockets,
+ xsmp_sockets + num_local_xsmp_sockets);
+ g_warning ("IceListenForConnections returned %d non-local listeners: %s",
+ num_xsmp_sockets - num_local_xsmp_sockets, network_id_list);
+ free (network_id_list);
+ }
+#endif
+
+ /* Update .ICEauthority with new auth entries for our socket */
+ if (!update_iceauthority (TRUE))
+ {
+ /* FIXME: is this really fatal? Hm... */
+ g_error ("Could not update ICEauthority file %s",
+ IceAuthFileName ());
+ }
+
+ network_id_list = IceComposeNetworkIdList (num_local_xsmp_sockets,
+ xsmp_sockets);
+
+ return network_id_list;
+}
+
+/**
+ * gsm_xsmp_run:
+ *
+ * Sets the XSMP server to start accepting connections.
+ **/
+void
+gsm_xsmp_run (void)
+{
+ GIOChannel *channel;
+ int i;
+
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ {
+ channel = g_io_channel_unix_new (IceGetListenConnectionNumber (xsmp_sockets[i]));
+ g_io_add_watch (channel, G_IO_IN | G_IO_HUP | G_IO_ERR,
+ accept_ice_connection, xsmp_sockets[i]);
+ g_io_channel_unref (channel);
+ }
+}
+
+/**
+ * gsm_xsmp_shutdown:
+ *
+ * Shuts down the XSMP server and closes the ICE listening socket
+ **/
+void
+gsm_xsmp_shutdown (void)
+{
+ update_iceauthority (FALSE);
+
+ IceFreeListenObjs (num_xsmp_sockets, xsmp_sockets);
+ xsmp_sockets = NULL;
+}
+
+/**
+ * gsm_xsmp_generate_client_id:
+ *
+ * Generates a new XSMP client ID.
+ *
+ * Return value: an XSMP client ID.
+ **/
+char *
+gsm_xsmp_generate_client_id (void)
+{
+ static int sequence = -1;
+ static guint rand1 = 0, rand2 = 0;
+ static pid_t pid = 0;
+ struct timeval tv;
+
+ /* The XSMP spec defines the ID as:
+ *
+ * Version: "1"
+ * Address type and address:
+ * "1" + an IPv4 address as 8 hex digits
+ * "2" + a DECNET address as 12 hex digits
+ * "6" + an IPv6 address as 32 hex digits
+ * Time stamp: milliseconds since UNIX epoch as 13 decimal digits
+ * Process-ID type and process-ID:
+ * "1" + POSIX PID as 10 decimal digits
+ * Sequence number as 4 decimal digits
+ *
+ * XSMP client IDs are supposed to be globally unique: if
+ * SmsGenerateClientID() is unable to determine a network
+ * address for the machine, it gives up and returns %NULL.
+ * GNOME and KDE have traditionally used a fourth address
+ * format in this case:
+ * "0" + 16 random hex digits
+ *
+ * We don't even bother trying SmsGenerateClientID(), since the
+ * user's IP address is probably "192.168.1.*" anyway, so a random
+ * number is actually more likely to be globally unique.
+ */
+
+ if (!rand1)
+ {
+ rand1 = g_random_int ();
+ rand2 = g_random_int ();
+ pid = getpid ();
+ }
+
+ sequence = (sequence + 1) % 10000;
+ gettimeofday (&tv, NULL);
+ return g_strdup_printf ("10%.04x%.04x%.10lu%.3u%.10lu%.4d",
+ rand1, rand2,
+ (unsigned long) tv.tv_sec,
+ (unsigned) tv.tv_usec,
+ (unsigned long) pid,
+ sequence);
+}
+
+/* This is called (by glib via xsmp->ice_connection_watch) when a
+ * connection is first received on the ICE listening socket. (We
+ * expect that the client will then initiate XSMP on the connection;
+ * if it does not, GsmClientXSMP will eventually time out and close
+ * the connection.)
+ *
+ * FIXME: it would probably make more sense to not create a
+ * GsmClientXSMP object until accept_xsmp_connection, below (and to do
+ * the timing-out here in xsmp.c).
+ */
+static gboolean
+accept_ice_connection (GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ IceListenObj listener = data;
+ IceConn ice_conn;
+ IceAcceptStatus status;
+ GsmClientXSMP *client;
+
+ g_debug ("accept_ice_connection()");
+
+ ice_conn = IceAcceptConnection (listener, &status);
+ if (status != IceAcceptSuccess)
+ {
+ g_debug ("IceAcceptConnection returned %d", status);
+ return TRUE;
+ }
+
+ client = gsm_client_xsmp_new (ice_conn);
+ ice_conn->context = client;
+ return TRUE;
+}
+
+/* This is called (by libSM) when XSMP is initiated on an ICE
+ * connection that was already accepted by accept_ice_connection.
+ */
+static Status
+accept_xsmp_connection (SmsConn sms_conn, SmPointer manager_data,
+ unsigned long *mask_ret, SmsCallbacks *callbacks_ret,
+ char **failure_reason_ret)
+{
+ IceConn ice_conn;
+ GsmClientXSMP *client;
+
+ /* FIXME: what about during shutdown but before gsm_xsmp_shutdown? */
+ if (!xsmp_sockets)
+ {
+ g_debug ("In shutdown, rejecting new client");
+
+ *failure_reason_ret =
+ strdup (_("Refusing new client connection because the session is currently being shut down\n"));
+ return FALSE;
+ }
+
+ ice_conn = SmsGetIceConnection (sms_conn);
+ client = ice_conn->context;
+
+ g_return_val_if_fail (client != NULL, TRUE);
+
+ gsm_client_xsmp_connect (client, sms_conn, mask_ret, callbacks_ret);
+ return TRUE;
+}
+
+/* ICEauthority stuff */
+
+/* Various magic numbers stolen from iceauth.c */
+#define GSM_ICE_AUTH_RETRIES 10
+#define GSM_ICE_AUTH_INTERVAL 2 /* 2 seconds */
+#define GSM_ICE_AUTH_LOCK_TIMEOUT 600 /* 10 minutes */
+
+#define GSM_ICE_MAGIC_COOKIE_AUTH_NAME "MIT-MAGIC-COOKIE-1"
+#define GSM_ICE_MAGIC_COOKIE_LEN 16
+
+static IceAuthFileEntry *
+auth_entry_new (const char *protocol, const char *network_id)
+{
+ IceAuthFileEntry *file_entry;
+ IceAuthDataEntry data_entry;
+
+ file_entry = malloc (sizeof (IceAuthFileEntry));
+
+ file_entry->protocol_name = strdup (protocol);
+ file_entry->protocol_data = NULL;
+ file_entry->protocol_data_length = 0;
+ file_entry->network_id = strdup (network_id);
+ file_entry->auth_name = strdup (GSM_ICE_MAGIC_COOKIE_AUTH_NAME);
+ file_entry->auth_data = IceGenerateMagicCookie (GSM_ICE_MAGIC_COOKIE_LEN);
+ file_entry->auth_data_length = GSM_ICE_MAGIC_COOKIE_LEN;
+
+ /* Also create an in-memory copy, which is what the server will
+ * actually use for checking client auth.
+ */
+ data_entry.protocol_name = file_entry->protocol_name;
+ data_entry.network_id = file_entry->network_id;
+ data_entry.auth_name = file_entry->auth_name;
+ data_entry.auth_data = file_entry->auth_data;
+ data_entry.auth_data_length = file_entry->auth_data_length;
+ IceSetPaAuthData (1, &data_entry);
+
+ return file_entry;
+}
+
+static gboolean
+update_iceauthority (gboolean adding)
+{
+ char *filename = IceAuthFileName ();
+ char **our_network_ids;
+ FILE *fp;
+ IceAuthFileEntry *auth_entry;
+ GSList *entries, *e;
+ int i;
+ gboolean ok = FALSE;
+
+ if (IceLockAuthFile (filename, GSM_ICE_AUTH_RETRIES, GSM_ICE_AUTH_INTERVAL,
+ GSM_ICE_AUTH_LOCK_TIMEOUT) != IceAuthLockSuccess)
+ return FALSE;
+
+ our_network_ids = g_malloc (num_local_xsmp_sockets * sizeof (char *));
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ our_network_ids[i] = IceGetListenConnectionString (xsmp_sockets[i]);
+
+ entries = NULL;
+
+ fp = fopen (filename, "r+");
+ if (fp)
+ {
+ while ((auth_entry = IceReadAuthFileEntry (fp)) != NULL)
+ {
+ /* Skip/delete entries with no network ID (invalid), or with
+ * our network ID; if we're starting up, an entry with our
+ * ID must be a stale entry left behind by an old process,
+ * and if we're shutting down, it won't be valid in the
+ * future, so either way we want to remove it from the list.
+ */
+ if (!auth_entry->network_id)
+ {
+ IceFreeAuthFileEntry (auth_entry);
+ continue;
+ }
+
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ {
+ if (!strcmp (auth_entry->network_id, our_network_ids[i]))
+ {
+ IceFreeAuthFileEntry (auth_entry);
+ break;
+ }
+ }
+ if (i != num_local_xsmp_sockets)
+ continue;
+
+ entries = g_slist_prepend (entries, auth_entry);
+ }
+
+ rewind (fp);
+ }
+ else
+ {
+ int fd;
+
+ if (g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ g_warning ("Unable to read ICE authority file: %s", filename);
+ goto cleanup;
+ }
+
+ fd = open (filename, O_CREAT | O_WRONLY, 0600);
+ fp = fdopen (fd, "w");
+ if (!fp)
+ {
+ g_warning ("Unable to write to ICE authority file: %s", filename);
+ if (fd != -1)
+ close (fd);
+ goto cleanup;
+ }
+ }
+
+ if (adding)
+ {
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ {
+ entries = g_slist_append (entries,
+ auth_entry_new ("ICE", our_network_ids[i]));
+ entries = g_slist_prepend (entries,
+ auth_entry_new ("XSMP", our_network_ids[i]));
+ }
+ }
+
+ for (e = entries; e; e = e->next)
+ {
+ IceAuthFileEntry *auth_entry = e->data;
+ IceWriteAuthFileEntry (fp, auth_entry);
+ IceFreeAuthFileEntry (auth_entry);
+ }
+ g_slist_free (entries);
+
+ fclose (fp);
+ ok = TRUE;
+
+ cleanup:
+ IceUnlockAuthFile (filename);
+ for (i = 0; i < num_local_xsmp_sockets; i++)
+ free (our_network_ids[i]);
+ g_free (our_network_ids);
+
+ return ok;
+}
+
+/* Error handlers */
+
+static void
+ice_error_handler (IceConn conn, Bool swap, int offending_minor_opcode,
+ unsigned long offending_sequence, int error_class,
+ int severity, IcePointer values)
+{
+ g_debug ("ice_error_handler (%p, %s, %d, %lx, %d, %d)",
+ conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
+ offending_sequence, error_class, severity);
+
+ if (severity == IceCanContinue)
+ return;
+
+ /* FIXME: the ICElib docs are completely vague about what we're
+ * supposed to do in this case. Need to verify that calling
+ * IceCloseConnection() here is guaranteed to cause neither
+ * free-memory-reads nor leaks.
+ */
+ IceCloseConnection (conn);
+}
+
+static void
+ice_io_error_handler (IceConn conn)
+{
+ g_debug ("ice_io_error_handler (%p)", conn);
+
+ /* We don't need to do anything here; the next call to
+ * IceProcessMessages() for this connection will receive
+ * IceProcessMessagesIOError and we can handle the error there.
+ */
+}
+
+static void
+sms_error_handler (SmsConn conn, Bool swap, int offending_minor_opcode,
+ unsigned long offending_sequence_num, int error_class,
+ int severity, IcePointer values)
+{
+ g_debug ("sms_error_handler (%p, %s, %d, %lx, %d, %d)",
+ conn, swap ? "TRUE" : "FALSE", offending_minor_opcode,
+ offending_sequence_num, error_class, severity);
+
+ /* We don't need to do anything here; if the connection needs to be
+ * closed, libSM will do that itself.
+ */
+}
diff --git a/src/sugar/xsmp.h b/src/sugar/xsmp.h
new file mode 100644
index 0000000..b4b535f
--- /dev/null
+++ b/src/sugar/xsmp.h
@@ -0,0 +1,29 @@
+/* xsmp.h
+ * Copyright (C) 2007 Novell, Inc.
+ *
+ * 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
+ * Lesser 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.
+ */
+
+#ifndef __GSM_XSMP_H__
+#define __GSM_XSMP_H__
+
+char *gsm_xsmp_init (void);
+void gsm_xsmp_run (void);
+void gsm_xsmp_shutdown (void);
+
+char *gsm_xsmp_generate_client_id (void);
+
+#endif /* __GSM_XSMP_H__ */