sugar-toolkit-gtk3/src/sugar3/eggsmclient-xsmp.c
Daniel Drake 4234ca684e Restore use of XSMP client
In the GTK3 port we mistakenly moved from using the EggSMClientXSMP
class to the (stub-like) EggSMClient base class for Sugar's XSMPClient
class, instantiated for every activity.

This meant that the GTK3 activities weren't registering with the
session manager, meaning that they won't automatically save their work
when the user shuts down, and they can't inhibit shutdown, etc.

Restore this functionality by adding the appropriate header so that
EggSMClientXSMP is introspectable, and then use it from the Python code.

Acked-by: Simon Schampijer <simon@laptop.org>
2012-11-23 19:39:49 +01:00

1298 lines
35 KiB
C

/*
* Copyright (C) 2007 Novell, Inc.
*
* Inspired by various other pieces of code including GsmClient (C)
* 2001 Havoc Pennington, GnomeClient (C) 1998 Carsten Schaar, and twm
* session code (C) 1998 The Open Group.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "eggsmclient.h"
#include "eggsmclient-xsmp.h"
#include "eggsmclient-private.h"
#include "eggdesktopfile.h"
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/SM/SMlib.h>
#include <gdk/gdk.h>
static const char *state_names[] = {
"start",
"idle",
"save-yourself",
"interact-request",
"interact",
"save-yourself-done",
"shutdown-cancelled",
"connection-closed"
};
#define EGG_SM_CLIENT_XSMP_STATE(xsmp) (state_names[(xsmp)->state])
static void sm_client_xsmp_startup (EggSMClient *client,
const char *client_id);
static void sm_client_xsmp_set_restart_command (EggSMClient *client,
int argc,
const char **argv);
static void sm_client_xsmp_will_quit (EggSMClient *client,
gboolean will_quit);
static gboolean sm_client_xsmp_end_session (EggSMClient *client,
EggSMClientEndStyle style,
gboolean request_confirmation);
static void xsmp_save_yourself (SmcConn smc_conn,
SmPointer client_data,
int save_style,
Bool shutdown,
int interact_style,
Bool fast);
static void xsmp_die (SmcConn smc_conn,
SmPointer client_data);
static void xsmp_save_complete (SmcConn smc_conn,
SmPointer client_data);
static void xsmp_shutdown_cancelled (SmcConn smc_conn,
SmPointer client_data);
static void xsmp_interact (SmcConn smc_conn,
SmPointer client_data);
static SmProp *array_prop (const char *name,
...);
static SmProp *ptrarray_prop (const char *name,
GPtrArray *values);
static SmProp *string_prop (const char *name,
const char *value);
static SmProp *card8_prop (const char *name,
unsigned char value);
static void set_properties (EggSMClientXSMP *xsmp, ...);
static void delete_properties (EggSMClientXSMP *xsmp, ...);
static GPtrArray *generate_command (char **restart_command,
const char *client_id,
const char *state_file);
static void save_state (EggSMClientXSMP *xsmp);
static void do_save_yourself (EggSMClientXSMP *xsmp);
static void update_pending_events (EggSMClientXSMP *xsmp);
static void ice_init (void);
static gboolean process_ice_messages (IceConn ice_conn);
static void smc_error_handler (SmcConn smc_conn,
Bool swap,
int offending_minor_opcode,
unsigned long offending_sequence,
int error_class,
int severity,
SmPointer values);
G_DEFINE_TYPE (EggSMClientXSMP, egg_sm_client_xsmp, EGG_TYPE_SM_CLIENT)
static void
egg_sm_client_xsmp_init (EggSMClientXSMP *xsmp)
{
xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
xsmp->connection = NULL;
xsmp->restart_style = SmRestartIfRunning;
xsmp->client_id = NULL;
}
static void
egg_sm_client_xsmp_class_init (EggSMClientXSMPClass *klass)
{
EggSMClientClass *sm_client_class = EGG_SM_CLIENT_CLASS (klass);
sm_client_class->startup = sm_client_xsmp_startup;
sm_client_class->set_restart_command = sm_client_xsmp_set_restart_command;
sm_client_class->will_quit = sm_client_xsmp_will_quit;
sm_client_class->end_session = sm_client_xsmp_end_session;
}
EggSMClient *
egg_sm_client_xsmp_new (void)
{
if (!g_getenv ("SESSION_MANAGER"))
return NULL;
return g_object_new (EGG_TYPE_SM_CLIENT_XSMP, NULL);
}
static gboolean
sm_client_xsmp_connect (gpointer user_data)
{
EggSMClientXSMP *xsmp = user_data;
SmcCallbacks callbacks;
char *client_id;
char error_string_ret[256];
char pid_str[64];
EggDesktopFile *desktop_file;
GPtrArray *clone, *restart;
g_source_remove (xsmp->idle);
xsmp->idle = 0;
ice_init ();
SmcSetErrorHandler (smc_error_handler);
callbacks.save_yourself.callback = xsmp_save_yourself;
callbacks.die.callback = xsmp_die;
callbacks.save_complete.callback = xsmp_save_complete;
callbacks.shutdown_cancelled.callback = xsmp_shutdown_cancelled;
callbacks.save_yourself.client_data = xsmp;
callbacks.die.client_data = xsmp;
callbacks.save_complete.client_data = xsmp;
callbacks.shutdown_cancelled.client_data = xsmp;
client_id = NULL;
error_string_ret[0] = '\0';
xsmp->connection =
SmcOpenConnection (NULL, xsmp, SmProtoMajor, SmProtoMinor,
SmcSaveYourselfProcMask | SmcDieProcMask |
SmcSaveCompleteProcMask |
SmcShutdownCancelledProcMask,
&callbacks,
//xsmp->client_id, &client_id,
NULL, &client_id,
sizeof (error_string_ret), error_string_ret);
if (!xsmp->connection)
{
g_warning ("Failed to connect to the session manager: %s\n",
error_string_ret[0] ?
error_string_ret : "no error message given");
xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
return FALSE;
}
/* We expect a pointless initial SaveYourself if either (a) we
* didn't have an initial client ID, or (b) we DID have an initial
* client ID, but the server rejected it and gave us a new one.
*/
if (!xsmp->client_id ||
(client_id && strcmp (xsmp->client_id, client_id) != 0))
xsmp->expecting_initial_save_yourself = TRUE;
if (client_id)
{
g_free (xsmp->client_id);
xsmp->client_id = g_strdup (client_id);
free (client_id);
gdk_threads_enter ();
gdk_x11_set_sm_client_id (xsmp->client_id);
gdk_threads_leave ();
g_debug ("Got client ID \"%s\"", xsmp->client_id);
}
/* Parse info out of desktop file */
desktop_file = egg_get_desktop_file ();
if (desktop_file)
{
GError *err = NULL;
char *cmdline, **argv;
int argc;
if (xsmp->restart_style == SmRestartIfRunning)
{
if (egg_desktop_file_get_boolean (desktop_file,
"X-GNOME-AutoRestart", NULL))
xsmp->restart_style = SmRestartImmediately;
}
if (!xsmp->set_restart_command)
{
cmdline = egg_desktop_file_parse_exec (desktop_file, NULL, &err);
if (cmdline && g_shell_parse_argv (cmdline, &argc, &argv, &err))
{
egg_sm_client_set_restart_command (EGG_SM_CLIENT (xsmp),
argc, (const char **)argv);
g_strfreev (argv);
}
else
{
g_warning ("Could not parse Exec line in desktop file: %s",
err->message);
g_error_free (err);
}
}
}
if (!xsmp->set_restart_command)
xsmp->restart_command = g_strsplit (g_get_prgname (), " ", -1);
clone = generate_command (xsmp->restart_command, NULL, NULL);
restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
g_debug ("Setting initial properties");
/* Program, CloneCommand, RestartCommand, and UserID are required.
* ProcessID isn't required, but the SM may be able to do something
* useful with it.
*/
g_snprintf (pid_str, sizeof (pid_str), "%lu", (gulong) getpid ());
set_properties (xsmp,
string_prop (SmProgram, g_get_prgname ()),
ptrarray_prop (SmCloneCommand, clone),
ptrarray_prop (SmRestartCommand, restart),
string_prop (SmUserID, g_get_user_name ()),
string_prop (SmProcessID, pid_str),
card8_prop (SmRestartStyleHint, xsmp->restart_style),
NULL);
g_ptr_array_free (clone, TRUE);
g_ptr_array_free (restart, TRUE);
if (desktop_file)
{
set_properties (xsmp,
string_prop ("_GSM_DesktopFile", egg_desktop_file_get_source (desktop_file)),
NULL);
}
xsmp->state = XSMP_STATE_IDLE;
return FALSE;
}
/* This gets called from two different places: xsmp_die() (when the
* server asks us to disconnect) and process_ice_messages() (when the
* server disconnects unexpectedly).
*/
static void
sm_client_xsmp_disconnect (EggSMClientXSMP *xsmp)
{
SmcConn connection;
if (!xsmp->connection)
return;
g_debug ("Disconnecting");
connection = xsmp->connection;
xsmp->connection = NULL;
SmcCloseConnection (connection, 0, NULL);
xsmp->state = XSMP_STATE_CONNECTION_CLOSED;
xsmp->waiting_to_save_myself = FALSE;
update_pending_events (xsmp);
}
static void
sm_client_xsmp_startup (EggSMClient *client,
const char *client_id)
{
EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
xsmp->state = XSMP_STATE_START;
if (xsmp->client_id)
g_free (xsmp->client_id);
xsmp->client_id = g_strdup (client_id);
/* Don't connect to the session manager until we reach the main
* loop, since the session manager may assume we're fully up and
* running once we connect. (This also gives the application a
* chance to call egg_set_desktop_file() before we set the initial
* properties.)
*/
xsmp->idle = g_idle_add (sm_client_xsmp_connect, client);
}
static void
sm_client_xsmp_set_restart_command (EggSMClient *client,
int argc,
const char **argv)
{
EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
int i;
g_strfreev (xsmp->restart_command);
xsmp->restart_command = g_new (char *, argc + 1);
for (i = 0; i < argc; i++)
xsmp->restart_command[i] = g_strdup (argv[i]);
xsmp->restart_command[i] = NULL;
xsmp->set_restart_command = TRUE;
}
static void
sm_client_xsmp_will_quit (EggSMClient *client,
gboolean will_quit)
{
EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
if (xsmp->state == XSMP_STATE_CONNECTION_CLOSED)
{
/* The session manager has already exited! Schedule a quit
* signal.
*/
xsmp->waiting_to_emit_quit = TRUE;
update_pending_events (xsmp);
return;
}
else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
{
/* We received a ShutdownCancelled message while the application
* was interacting; Schedule a quit_cancelled signal.
*/
xsmp->waiting_to_emit_quit_cancelled = TRUE;
update_pending_events (xsmp);
return;
}
g_return_if_fail (xsmp->state == XSMP_STATE_INTERACT);
g_debug ("Sending InteractDone(%s)", will_quit ? "False" : "True");
SmcInteractDone (xsmp->connection, !will_quit);
if (will_quit && xsmp->need_save_state)
save_state (xsmp);
g_debug ("Sending SaveYourselfDone(%s)", will_quit ? "True" : "False");
SmcSaveYourselfDone (xsmp->connection, will_quit);
xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
}
static gboolean
sm_client_xsmp_end_session (EggSMClient *client,
EggSMClientEndStyle style,
gboolean request_confirmation)
{
EggSMClientXSMP *xsmp = (EggSMClientXSMP *)client;
int save_type;
/* To end the session via XSMP, we have to send a
* SaveYourselfRequest. We aren't allowed to do that if anything
* else is going on, but we don't want to expose this fact to the
* application. So we do our best to patch things up here...
*
* In the worst case, this method might block for some length of
* time in process_ice_messages, but the only time that code path is
* honestly likely to get hit is if the application tries to end the
* session as the very first thing it does, in which case it
* probably won't actually block anyway. It's not worth gunking up
* the API to try to deal nicely with the other 0.01% of cases where
* this happens.
*/
while (xsmp->state != XSMP_STATE_IDLE ||
xsmp->expecting_initial_save_yourself)
{
/* If we're already shutting down, we don't need to do anything. */
if (xsmp->shutting_down)
return TRUE;
switch (xsmp->state)
{
case XSMP_STATE_START:
/* Force the connection to complete (or fail) now. */
sm_client_xsmp_connect (xsmp);
break;
case XSMP_STATE_CONNECTION_CLOSED:
return FALSE;
case XSMP_STATE_SAVE_YOURSELF:
/* Trying to log out from the save_state callback? Whatever.
* Abort the save_state.
*/
SmcSaveYourselfDone (xsmp->connection, FALSE);
xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
break;
case XSMP_STATE_INTERACT_REQUEST:
case XSMP_STATE_INTERACT:
case XSMP_STATE_SHUTDOWN_CANCELLED:
/* Already in a shutdown-related state, just ignore
* the new shutdown request...
*/
return TRUE;
case XSMP_STATE_IDLE:
if (!xsmp->expecting_initial_save_yourself)
break;
/* else fall through */
case XSMP_STATE_SAVE_YOURSELF_DONE:
/* We need to wait for some response from the server.*/
process_ice_messages (SmcGetIceConnection (xsmp->connection));
break;
default:
/* Hm... shouldn't happen */
return FALSE;
}
}
/* xfce4-session will do the wrong thing if we pass SmSaveGlobal and
* the user chooses to save the session. But gnome-session will do
* the wrong thing if we pass SmSaveBoth and the user chooses NOT to
* save the session... Sigh.
*/
if (!strcmp (SmcVendor (xsmp->connection), "xfce4-session"))
save_type = SmSaveBoth;
else
save_type = SmSaveGlobal;
g_debug ("Sending SaveYourselfRequest(SmSaveGlobal, Shutdown, SmInteractStyleAny, %sFast)", request_confirmation ? "!" : "");
SmcRequestSaveYourself (xsmp->connection,
save_type,
True, /* shutdown */
SmInteractStyleAny,
!request_confirmation, /* fast */
True /* global */);
return TRUE;
}
static gboolean
idle_do_pending_events (gpointer data)
{
EggSMClientXSMP *xsmp = data;
EggSMClient *client = data;
gdk_threads_enter ();
xsmp->idle = 0;
if (xsmp->waiting_to_emit_quit)
{
xsmp->waiting_to_emit_quit = FALSE;
egg_sm_client_quit (client);
goto out;
}
if (xsmp->waiting_to_emit_quit_cancelled)
{
xsmp->waiting_to_emit_quit_cancelled = FALSE;
egg_sm_client_quit_cancelled (client);
xsmp->state = XSMP_STATE_IDLE;
}
if (xsmp->waiting_to_save_myself)
{
xsmp->waiting_to_save_myself = FALSE;
do_save_yourself (xsmp);
}
out:
gdk_threads_leave ();
return FALSE;
}
static void
update_pending_events (EggSMClientXSMP *xsmp)
{
gboolean want_idle =
xsmp->waiting_to_emit_quit ||
xsmp->waiting_to_emit_quit_cancelled ||
xsmp->waiting_to_save_myself;
if (want_idle)
{
if (xsmp->idle == 0)
xsmp->idle = g_idle_add (idle_do_pending_events, xsmp);
}
else
{
if (xsmp->idle != 0)
g_source_remove (xsmp->idle);
xsmp->idle = 0;
}
}
static void
fix_broken_state (EggSMClientXSMP *xsmp, const char *message,
gboolean send_interact_done,
gboolean send_save_yourself_done)
{
g_warning ("Received XSMP %s message in state %s: client or server error",
message, EGG_SM_CLIENT_XSMP_STATE (xsmp));
/* Forget any pending SaveYourself plans we had */
xsmp->waiting_to_save_myself = FALSE;
update_pending_events (xsmp);
if (send_interact_done)
SmcInteractDone (xsmp->connection, False);
if (send_save_yourself_done)
SmcSaveYourselfDone (xsmp->connection, True);
xsmp->state = send_save_yourself_done ? XSMP_STATE_SAVE_YOURSELF_DONE : XSMP_STATE_IDLE;
}
/* SM callbacks */
static void
xsmp_save_yourself (SmcConn smc_conn,
SmPointer client_data,
int save_type,
Bool shutdown,
int interact_style,
Bool fast)
{
EggSMClientXSMP *xsmp = client_data;
gboolean wants_quit_requested;
g_debug ("Received SaveYourself(%s, %s, %s, %s) in state %s",
save_type == SmSaveLocal ? "SmSaveLocal" :
save_type == SmSaveGlobal ? "SmSaveGlobal" : "SmSaveBoth",
shutdown ? "Shutdown" : "!Shutdown",
interact_style == SmInteractStyleAny ? "SmInteractStyleAny" :
interact_style == SmInteractStyleErrors ? "SmInteractStyleErrors" :
"SmInteractStyleNone", fast ? "Fast" : "!Fast",
EGG_SM_CLIENT_XSMP_STATE (xsmp));
if (xsmp->state != XSMP_STATE_IDLE &&
xsmp->state != XSMP_STATE_SHUTDOWN_CANCELLED)
{
fix_broken_state (xsmp, "SaveYourself", FALSE, TRUE);
return;
}
/* If this is the initial SaveYourself, ignore it; we've already set
* properties and there's no reason to actually save state too.
*/
if (xsmp->expecting_initial_save_yourself)
{
xsmp->expecting_initial_save_yourself = FALSE;
if (save_type == SmSaveLocal &&
interact_style == SmInteractStyleNone &&
!shutdown && !fast)
{
g_debug ("Sending SaveYourselfDone(True) for initial SaveYourself");
SmcSaveYourselfDone (xsmp->connection, True);
/* As explained in the comment at the end of
* do_save_yourself(), SAVE_YOURSELF_DONE is the correct
* state here, not IDLE.
*/
xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
return;
}
else
g_warning ("First SaveYourself was not the expected one!");
}
/* Even ignoring the "fast" flag completely, there are still 18
* different combinations of save_type, shutdown and interact_style.
* We interpret them as follows:
*
* Type Shutdown Interact Interpretation
* G F A/E/N do nothing (1)
* G T N do nothing (1)*
* G T A/E quit_requested (2)
* L/B F A/E/N save_state (3)
* L/B T N save_state (3)*
* L/B T A/E quit_requested, then save_state (4)
*
* 1. Do nothing, because the SM asked us to do something
* uninteresting (save open files, but then don't quit
* afterward) or rude (save open files without asking the user
* for confirmation).
*
* 2. Request interaction and then emit ::quit_requested. This
* perhaps isn't quite correct for the SmInteractStyleErrors
* case, but we don't care.
*
* 3. Emit ::save_state. The SmSaveBoth SaveYourselfs in these
* rows essentially get demoted to SmSaveLocal, because their
* Global halves correspond to "do nothing".
*
* 4. Request interaction, emit ::quit_requested, and then emit
* ::save_state after interacting. This is the SmSaveBoth
* equivalent of #2, but we also promote SmSaveLocal shutdown
* SaveYourselfs to SmSaveBoth here, because we want to give
* the user a chance to save open files before quitting.
*
* (* It would be nice if we could do something useful when the
* session manager sends a SaveYourself with shutdown True and
* SmInteractStyleNone. But we can't, so we just pretend it didn't
* even tell us it was shutting down. The docs for ::quit mention
* that it might not always be preceded by ::quit_requested.)
*/
/* As an optimization, we don't actually request interaction and
* emit ::quit_requested if the application isn't listening to the
* signal.
*/
wants_quit_requested = g_signal_has_handler_pending (xsmp, g_signal_lookup ("quit_requested", EGG_TYPE_SM_CLIENT), 0, FALSE);
xsmp->need_save_state = (save_type != SmSaveGlobal);
xsmp->need_quit_requested = (shutdown && wants_quit_requested &&
interact_style != SmInteractStyleNone);
xsmp->interact_errors = (interact_style == SmInteractStyleErrors);
xsmp->shutting_down = shutdown;
do_save_yourself (xsmp);
}
static void
do_save_yourself (EggSMClientXSMP *xsmp)
{
if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
{
/* The SM cancelled a previous SaveYourself, but we haven't yet
* had a chance to tell the application, so we can't start
* processing this SaveYourself yet.
*/
xsmp->waiting_to_save_myself = TRUE;
update_pending_events (xsmp);
return;
}
if (xsmp->need_quit_requested)
{
xsmp->state = XSMP_STATE_INTERACT_REQUEST;
g_debug ("Sending InteractRequest(%s)",
xsmp->interact_errors ? "Error" : "Normal");
SmcInteractRequest (xsmp->connection,
xsmp->interact_errors ? SmDialogError : SmDialogNormal,
xsmp_interact,
xsmp);
return;
}
if (xsmp->need_save_state)
{
save_state (xsmp);
/* Though unlikely, the client could have been disconnected
* while the application was saving its state.
*/
if (!xsmp->connection)
return;
}
g_debug ("Sending SaveYourselfDone(True)");
SmcSaveYourselfDone (xsmp->connection, True);
/* The client state diagram in the XSMP spec says that after a
* non-shutdown SaveYourself, we go directly back to "idle". But
* everything else in both the XSMP spec and the libSM docs
* disagrees.
*/
xsmp->state = XSMP_STATE_SAVE_YOURSELF_DONE;
}
static void
save_state (EggSMClientXSMP *xsmp)
{
GKeyFile *state_file;
char *state_file_path, *data;
EggDesktopFile *desktop_file;
GPtrArray *restart;
int offset, fd;
/* We set xsmp->state before emitting save_state, but our caller is
* responsible for setting it back afterward.
*/
xsmp->state = XSMP_STATE_SAVE_YOURSELF;
state_file = egg_sm_client_save_state ((EggSMClient *)xsmp);
if (!state_file)
{
restart = generate_command (xsmp->restart_command, xsmp->client_id, NULL);
set_properties (xsmp,
ptrarray_prop (SmRestartCommand, restart),
NULL);
g_ptr_array_free (restart, TRUE);
delete_properties (xsmp, SmDiscardCommand, NULL);
return;
}
desktop_file = egg_get_desktop_file ();
if (desktop_file)
{
GKeyFile *merged_file;
merged_file = g_key_file_new ();
if (g_key_file_load_from_file (merged_file,
egg_desktop_file_get_source (desktop_file),
G_KEY_FILE_KEEP_COMMENTS |
G_KEY_FILE_KEEP_TRANSLATIONS, NULL))
{
int g, k, i;
char **groups, **keys, *value, *exec;
groups = g_key_file_get_groups (state_file, NULL);
for (g = 0; groups[g]; g++)
{
keys = g_key_file_get_keys (state_file, groups[g], NULL, NULL);
for (k = 0; keys[k]; k++)
{
value = g_key_file_get_value (state_file, groups[g],
keys[k], NULL);
if (value)
{
g_key_file_set_value (merged_file, groups[g],
keys[k], value);
g_free (value);
}
}
g_strfreev (keys);
}
g_strfreev (groups);
g_key_file_free (state_file);
state_file = merged_file;
/* Update Exec key using "--sm-client-state-file %k" */
restart = generate_command (xsmp->restart_command,
NULL, "%k");
for (i = 0; i < restart->len; i++)
restart->pdata[i] = g_shell_quote (restart->pdata[i]);
g_ptr_array_add (restart, NULL);
exec = g_strjoinv (" ", (char **)restart->pdata);
g_strfreev ((char **)restart->pdata);
g_ptr_array_free (restart, FALSE);
g_key_file_set_string (state_file, EGG_DESKTOP_FILE_GROUP,
EGG_DESKTOP_FILE_KEY_EXEC,
exec);
g_free (exec);
}
}
/* Now write state_file to disk. (We can't use mktemp(), because
* that requires the filename to end with "XXXXXX", and we want
* it to end with ".desktop".)
*/
data = g_key_file_to_data (state_file, NULL, NULL);
g_key_file_free (state_file);
offset = 0;
while (1)
{
state_file_path = g_strdup_printf ("%s%csession-state%c%s-%ld.%s",
g_get_user_config_dir (),
G_DIR_SEPARATOR, G_DIR_SEPARATOR,
g_get_prgname (),
(long)time (NULL) + offset,
desktop_file ? "desktop" : "state");
fd = open (state_file_path, O_WRONLY | O_CREAT | O_EXCL, 0644);
if (fd == -1)
{
if (errno == EEXIST)
{
offset++;
g_free (state_file_path);
continue;
}
else if (errno == ENOTDIR || errno == ENOENT)
{
char *sep = strrchr (state_file_path, G_DIR_SEPARATOR);
*sep = '\0';
if (g_mkdir_with_parents (state_file_path, 0755) != 0)
{
g_warning ("Could not create directory '%s'",
state_file_path);
g_free (state_file_path);
state_file_path = NULL;
break;
}
continue;
}
g_warning ("Could not create file '%s': %s",
state_file_path, g_strerror (errno));
g_free (state_file_path);
state_file_path = NULL;
break;
}
close (fd);
g_file_set_contents (state_file_path, data, -1, NULL);
break;
}
g_free (data);
restart = generate_command (xsmp->restart_command, xsmp->client_id,
state_file_path);
set_properties (xsmp,
ptrarray_prop (SmRestartCommand, restart),
NULL);
g_ptr_array_free (restart, TRUE);
if (state_file_path)
{
set_properties (xsmp,
array_prop (SmDiscardCommand,
"/bin/rm", "-rf", state_file_path,
NULL),
NULL);
g_free (state_file_path);
}
}
static void
xsmp_interact (SmcConn smc_conn,
SmPointer client_data)
{
EggSMClientXSMP *xsmp = client_data;
EggSMClient *client = client_data;
g_debug ("Received Interact message in state %s",
EGG_SM_CLIENT_XSMP_STATE (xsmp));
if (xsmp->state != XSMP_STATE_INTERACT_REQUEST)
{
fix_broken_state (xsmp, "Interact", TRUE, TRUE);
return;
}
xsmp->state = XSMP_STATE_INTERACT;
egg_sm_client_quit_requested (client);
}
static void
xsmp_die (SmcConn smc_conn,
SmPointer client_data)
{
EggSMClientXSMP *xsmp = client_data;
EggSMClient *client = client_data;
g_debug ("Received Die message in state %s",
EGG_SM_CLIENT_XSMP_STATE (xsmp));
sm_client_xsmp_disconnect (xsmp);
egg_sm_client_quit (client);
}
static void
xsmp_save_complete (SmcConn smc_conn,
SmPointer client_data)
{
EggSMClientXSMP *xsmp = client_data;
g_debug ("Received SaveComplete message in state %s",
EGG_SM_CLIENT_XSMP_STATE (xsmp));
if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
xsmp->state = XSMP_STATE_IDLE;
else
fix_broken_state (xsmp, "SaveComplete", FALSE, FALSE);
}
static void
xsmp_shutdown_cancelled (SmcConn smc_conn,
SmPointer client_data)
{
EggSMClientXSMP *xsmp = client_data;
EggSMClient *client = client_data;
g_debug ("Received ShutdownCancelled message in state %s",
EGG_SM_CLIENT_XSMP_STATE (xsmp));
xsmp->shutting_down = FALSE;
if (xsmp->state == XSMP_STATE_SAVE_YOURSELF_DONE)
{
/* We've finished interacting and now the SM has agreed to
* cancel the shutdown.
*/
xsmp->state = XSMP_STATE_IDLE;
egg_sm_client_quit_cancelled (client);
}
else if (xsmp->state == XSMP_STATE_SHUTDOWN_CANCELLED)
{
/* Hm... ok, so we got a shutdown SaveYourself, which got
* cancelled, but the application was still interacting, so we
* didn't tell it yet, and then *another* SaveYourself arrived,
* which we must still be waiting to tell the app about, except
* that now that SaveYourself has been cancelled too! Dizzy yet?
*/
xsmp->waiting_to_save_myself = FALSE;
update_pending_events (xsmp);
}
else
{
g_debug ("Sending SaveYourselfDone(False)");
SmcSaveYourselfDone (xsmp->connection, False);
if (xsmp->state == XSMP_STATE_INTERACT)
{
/* The application is currently interacting, so we can't
* tell it about the cancellation yet; we will wait until
* after it calls egg_sm_client_will_quit().
*/
xsmp->state = XSMP_STATE_SHUTDOWN_CANCELLED;
}
else
{
/* The shutdown was cancelled before the application got a
* chance to interact.
*/
xsmp->state = XSMP_STATE_IDLE;
}
}
}
/* Utilities */
/* Create a restart/clone/Exec command based on @restart_command.
* If @client_id is non-%NULL, add "--sm-client-id @client_id".
* If @state_file is non-%NULL, add "--sm-client-state-file @state_file".
*
* None of the input strings are g_strdup()ed; the caller must keep
* them around until it is done with the returned GPtrArray, and must
* then free the array, but not its contents.
*/
static GPtrArray *
generate_command (char **restart_command, const char *client_id,
const char *state_file)
{
GPtrArray *cmd;
int i;
cmd = g_ptr_array_new ();
g_ptr_array_add (cmd, restart_command[0]);
if (client_id)
{
g_ptr_array_add (cmd, "--sm-client-id");
g_ptr_array_add (cmd, (char *)client_id);
}
if (state_file)
{
g_ptr_array_add (cmd, "--sm-client-state-file");
g_ptr_array_add (cmd, (char *)state_file);
}
for (i = 1; restart_command[i]; i++)
g_ptr_array_add (cmd, restart_command[i]);
return cmd;
}
/* Takes a NULL-terminated list of SmProp * values, created by
* array_prop, ptrarray_prop, string_prop, card8_prop, sets them, and
* frees them.
*/
static void
set_properties (EggSMClientXSMP *xsmp, ...)
{
GPtrArray *props;
SmProp *prop;
va_list ap;
int i;
props = g_ptr_array_new ();
va_start (ap, xsmp);
while ((prop = va_arg (ap, SmProp *)))
g_ptr_array_add (props, prop);
va_end (ap);
if (xsmp->connection)
{
SmcSetProperties (xsmp->connection, props->len,
(SmProp **)props->pdata);
}
for (i = 0; i < props->len; i++)
{
prop = props->pdata[i];
g_free (prop->vals);
g_free (prop);
}
g_ptr_array_free (props, TRUE);
}
/* Takes a NULL-terminated list of property names and deletes them. */
static void
delete_properties (EggSMClientXSMP *xsmp, ...)
{
GPtrArray *props;
char *prop;
va_list ap;
if (!xsmp->connection)
return;
props = g_ptr_array_new ();
va_start (ap, xsmp);
while ((prop = va_arg (ap, char *)))
g_ptr_array_add (props, prop);
va_end (ap);
SmcDeleteProperties (xsmp->connection, props->len,
(char **)props->pdata);
g_ptr_array_free (props, TRUE);
}
/* Takes an array of strings and creates a LISTofARRAY8 property. The
* strings are neither dupped nor freed; they need to remain valid
* until you're done with the SmProp.
*/
static SmProp *
array_prop (const char *name, ...)
{
SmProp *prop;
SmPropValue pv;
GArray *vals;
char *value;
va_list ap;
prop = g_new (SmProp, 1);
prop->name = (char *)name;
prop->type = SmLISTofARRAY8;
vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
va_start (ap, name);
while ((value = va_arg (ap, char *)))
{
pv.length = strlen (value);
pv.value = value;
g_array_append_val (vals, pv);
}
prop->num_vals = vals->len;
prop->vals = (SmPropValue *)vals->data;
g_array_free (vals, FALSE);
return prop;
}
/* Takes a GPtrArray of strings and creates a LISTofARRAY8 property.
* The array contents are neither dupped nor freed; they need to
* remain valid until you're done with the SmProp.
*/
static SmProp *
ptrarray_prop (const char *name, GPtrArray *values)
{
SmProp *prop;
SmPropValue pv;
GArray *vals;
int i;
prop = g_new (SmProp, 1);
prop->name = (char *)name;
prop->type = SmLISTofARRAY8;
vals = g_array_new (FALSE, FALSE, sizeof (SmPropValue));
for (i = 0; i < values->len; i++)
{
pv.length = strlen (values->pdata[i]);
pv.value = values->pdata[i];
g_array_append_val (vals, pv);
}
prop->num_vals = vals->len;
prop->vals = (SmPropValue *)vals->data;
g_array_free (vals, FALSE);
return prop;
}
/* Takes a string and creates an ARRAY8 property. The string is
* neither dupped nor freed; it needs to remain valid until you're
* done with the SmProp.
*/
static SmProp *
string_prop (const char *name, const char *value)
{
SmProp *prop;
prop = g_new (SmProp, 1);
prop->name = (char *)name;
prop->type = SmARRAY8;
prop->num_vals = 1;
prop->vals = g_new (SmPropValue, 1);
prop->vals[0].length = strlen (value);
prop->vals[0].value = (char *)value;
return prop;
}
/* Takes a char and creates a CARD8 property. */
static SmProp *
card8_prop (const char *name, unsigned char value)
{
SmProp *prop;
char *card8val;
/* To avoid having to allocate and free prop->vals[0], we cheat and
* make vals a 2-element-long array and then use the second element
* to store value.
*/
prop = g_new (SmProp, 1);
prop->name = (char *)name;
prop->type = SmCARD8;
prop->num_vals = 1;
prop->vals = g_new (SmPropValue, 2);
card8val = (char *)(&prop->vals[1]);
card8val[0] = value;
prop->vals[0].length = 1;
prop->vals[0].value = card8val;
return prop;
}
/* ICE code. This makes no effort to play nice with anyone else trying
* to use libICE. Fortunately, no one uses libICE for anything other
* than SM. (DCOP uses ICE, but it has its own private copy of
* libICE.)
*
* When this moves to gtk, it will need to be cleverer, to avoid
* tripping over old apps that use GnomeClient or that use libSM
* directly.
*/
#include <X11/ICE/ICElib.h>
#include <fcntl.h>
static void ice_error_handler (IceConn ice_conn,
Bool swap,
int offending_minor_opcode,
unsigned long offending_sequence,
int error_class,
int severity,
IcePointer values);
static void ice_io_error_handler (IceConn ice_conn);
static void ice_connection_watch (IceConn ice_conn,
IcePointer client_data,
Bool opening,
IcePointer *watch_data);
static void
ice_init (void)
{
IceSetIOErrorHandler (ice_io_error_handler);
IceSetErrorHandler (ice_error_handler);
IceAddConnectionWatch (ice_connection_watch, NULL);
}
static gboolean
process_ice_messages (IceConn ice_conn)
{
IceProcessMessagesStatus status;
gdk_threads_enter ();
status = IceProcessMessages (ice_conn, NULL, NULL);
gdk_threads_leave ();
switch (status)
{
case IceProcessMessagesSuccess:
return TRUE;
case IceProcessMessagesIOError:
sm_client_xsmp_disconnect (IceGetConnectionContext (ice_conn));
return FALSE;
case IceProcessMessagesConnectionClosed:
return FALSE;
default:
g_assert_not_reached ();
}
}
static gboolean
ice_iochannel_watch (GIOChannel *channel,
GIOCondition condition,
gpointer client_data)
{
return process_ice_messages (client_data);
}
static void
ice_connection_watch (IceConn ice_conn,
IcePointer client_data,
Bool opening,
IcePointer *watch_data)
{
guint watch_id;
if (opening)
{
GIOChannel *channel;
int fd = IceConnectionNumber (ice_conn);
fcntl (fd, F_SETFD, fcntl (fd, F_GETFD, 0) | FD_CLOEXEC);
channel = g_io_channel_unix_new (fd);
watch_id = g_io_add_watch (channel, G_IO_IN | G_IO_ERR,
ice_iochannel_watch, ice_conn);
g_io_channel_unref (channel);
*watch_data = GUINT_TO_POINTER (watch_id);
}
else
{
watch_id = GPOINTER_TO_UINT (*watch_data);
g_source_remove (watch_id);
}
}
static void
ice_error_handler (IceConn ice_conn,
Bool swap,
int offending_minor_opcode,
unsigned long offending_sequence,
int error_class,
int severity,
IcePointer values)
{
/* Do nothing */
}
static void
ice_io_error_handler (IceConn ice_conn)
{
/* Do nothing */
}
static void
smc_error_handler (SmcConn smc_conn,
Bool swap,
int offending_minor_opcode,
unsigned long offending_sequence,
int error_class,
int severity,
SmPointer values)
{
/* Do nothing */
}