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 :)
This commit is contained in:
Marco Pesenti Gritti 2008-06-06 19:13:10 +02:00
parent 5ba50d0e0f
commit 50f79e6323
21 changed files with 6670 additions and 2 deletions

View File

@ -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 \
@ -45,6 +62,7 @@ _sugarext.c: _sugarext.defs _sugarext.override
.defs.c:
(cd $(srcdir)\
&& $(PYGTK_CODEGEN) \
--register $(PYGTK_DEFSDIR)/gdk-types.defs \
--register $(PYGTK_DEFSDIR)/gdk-types.defs \
--register $(PYGTK_DEFSDIR)/gtk-types.defs \
--override $*.override \

View File

@ -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*")
)

View File

@ -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>

View File

@ -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)

396
src/sugar/app.c Normal file
View File

@ -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);
}

70
src/sugar/app.h Normal file
View File

@ -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__ */

828
src/sugar/client-xsmp.c Normal file
View File

@ -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;
}

70
src/sugar/client-xsmp.h Normal file
View File

@ -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__ */

251
src/sugar/client.c Normal file
View File

@ -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);
}

111
src/sugar/client.h Normal file
View File

@ -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__ */

1437
src/sugar/eggdesktopfile.c Normal file

File diff suppressed because it is too large Load Diff

156
src/sugar/eggdesktopfile.h Normal file
View File

@ -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__ */

View File

@ -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__ */

1359
src/sugar/eggsmclient-xsmp.c Normal file

File diff suppressed because it is too large Load Diff

392
src/sugar/eggsmclient.c Normal file
View File

@ -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);
}

112
src/sugar/eggsmclient.h Normal file
View File

@ -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__ */

550
src/sugar/session.c Normal file
View File

@ -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;
}

93
src/sugar/session.h Normal file
View File

@ -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__ */

46
src/sugar/session.py Normal file
View File

@ -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()

537
src/sugar/xsmp.c Normal file
View File

@ -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.
*/
}

29
src/sugar/xsmp.h Normal file
View File

@ -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__ */