/* $Xorg: xsm.c,v 1.7 2001/02/09 02:06:01 xorgcvs Exp $ */
/******************************************************************************

Copyright 1993, 1998  The Open Group

Permission to use, copy, modify, distribute, and sell this software and its
documentation for any purpose is hereby granted without fee, provided that
the above copyright notice appear in all copies and that both that
copyright notice and this permission notice appear in supporting
documentation.

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
OPEN GROUP BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of The Open Group shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from The Open Group.
******************************************************************************/
/* $XFree86: xc/programs/xsm/xsm.c,v 1.9 2001/12/14 20:02:27 dawes Exp $ */

/*
 * X Session Manager.
 *
 * Authors:
 *	Ralph Mor, X Consortium
 *      Jordan Brown, Quarterdeck Office Systems
 */

#include "xsm.h"
#include "xtwatch.h"
#include "prop.h"
#include "choose.h"
#include "mainwin.h"
#include "info.h"
#include "log.h"
#include "save.h"
#include "auth.h"
#include "restart.h"
#include "saveutil.h"
#include "lock.h"

#include <X11/Shell.h>
#include <X11/Xatom.h>
#include <X11/Xaw/List.h>

int		Argc;
char		**Argv;

List		*RunningList;
List		*PendingList;
List		*RestartAnywayList;
List		*RestartImmedList;

List		*WaitForSaveDoneList;
static List	*InitialSaveList;
List		*FailedSaveList;
List		*WaitForInteractList;
List		*WaitForPhase2List;

Bool		wantShutdown = False;
Bool		shutdownInProgress = False;
Bool		phase2InProgress = False;
Bool		saveInProgress = False;
Bool		shutdownCancelled = False;

Bool		verbose = False;

char		*sm_id = NULL;

char		*networkIds = NULL;
char		*session_name = NULL;

IceAuthDataEntry *authDataEntries = NULL;
int		numTransports = 0;

Bool		client_info_visible = False;
Bool		client_prop_visible = False;
Bool		client_log_visible = False;

String 		*clientListNames = NULL;
ClientRec	**clientListRecs = NULL;
int		numClientListNames = 0;

int		current_client_selected;

int		sessionNameCount = 0;
String		*sessionNamesShort = NULL;
String		*sessionNamesLong = NULL;
Bool		*sessionsLocked = NULL;

int		num_clients_in_last_session = -1;

char		**non_session_aware_clients = NULL;
int		non_session_aware_count = 0;

char		*display_env = NULL, *non_local_display_env = NULL;
char		*session_env = NULL, *non_local_session_env = NULL;
char		*audio_env = NULL;

Bool		need_to_name_session = False;

Bool		remote_allowed;

XtAppContext	appContext;
Widget		topLevel;

XtSignalId	sig_term_id, sig_usr1_id;

static Atom wmStateAtom;
static Atom wmDeleteAtom;
static char *cmd_line_display = NULL;

/*
 * Forward declarations
 */
static void PropertyChangeXtHandler(Widget w, XtPointer closure, 
				    XEvent *event, 
				    Boolean *continue_to_dispatch);
static void GetEnvironment(void);
static Status RegisterClientProc(SmsConn smsConn, SmPointer managerData, 
				 char *previousId);
static Bool OkToEnterInteractPhase(void);
static void InteractRequestProc(SmsConn smsConn, SmPointer managerData, 
				int dialogType);
static void InteractDoneProc(SmsConn smsConn, SmPointer managerData, 
			     Bool cancelShutdown);
static void SaveYourselfReqProc(SmsConn smsConn, SmPointer managerData, 
				int saveType, Bool shutdown, 
				int interactStyle, Bool fast, Bool global);
static Bool OkToEnterPhase2(void);
static void SaveYourselfPhase2ReqProc(SmsConn smsConn, SmPointer managerData);
static void SaveYourselfDoneProc(SmsConn smsConn, SmPointer managerData, 
				 Bool success);
static void CloseConnectionProc(SmsConn smsConn, SmPointer managerData, 
				int count, char **reasonMsgs);
static Status NewClientProc(SmsConn smsConn, SmPointer managerData, 
			    unsigned long *maskRet, 
			    SmsCallbacks *callbacksRet, 
			    char **failureReasonRet);
static void NewConnectionXtProc(XtPointer client_data, int *source, 
				XtInputId *id);
static void MyIoErrorHandler(IceConn ice_conn);
static void InstallIOErrorHandler(void);
static void CloseListeners(void);


static IceListenObj *listenObjs;


/*
 * Main program
 */
int
main(int argc, char *argv[])
{
    char	*p;
    char 	errormsg[256];
    static	char environment_name[] = "SESSION_MANAGER";
    int		success, found_command_line_name, i;

    Argc = argc;
    Argv = argv;

    for (i = 1; i < argc; i++)
    {
	if (argv[i][0] == '-')
	{
	    switch (argv[i][1])
	    {
	    case 'd':					/* -display */
		if (++i >= argc) goto usage;
		cmd_line_display = (char *) XtNewString (argv[i]);
		continue;

	    case 's':					/* -session */
		if (++i >= argc) goto usage;
		session_name = XtNewString (argv[i]);
		continue;

	    case 'v':					/* -verbose */
		verbose = 1;
		continue;
	    }
	}

    usage:
	fprintf (stderr,
	 "usage: xsm [-display display] [-session session_name] [-verbose]\n");
	exit (1);
    }

    topLevel = XtVaAppInitialize (&appContext, "XSm", NULL, 0,
	&argc, argv, NULL,
	XtNmappedWhenManaged, False,
	XtNwindowRole, "xsm main window",
	NULL);
	
    wmStateAtom = XInternAtom (
	XtDisplay (topLevel), "WM_STATE", False);
    wmDeleteAtom = XInternAtom (
	XtDisplay (topLevel), "WM_DELETE_WINDOW", False);

    register_signals (appContext);


    /*
     * Install an IO error handler.  For an explanation,
     * see the comments for InstallIOErrorHandler().
     */

    InstallIOErrorHandler ();


    /*
     * Init SM lib
     */

    if (!SmsInitialize ("SAMPLE-SM", "1.0",
	NewClientProc, NULL,
	HostBasedAuthProc, 256, errormsg))
    {
	fprintf (stderr, "%s\n", errormsg);
	exit (1);
    }

    if (!IceListenForConnections (&numTransports, &listenObjs,
	256, errormsg))
    {
	fprintf (stderr, "%s\n", errormsg);
	exit (1);
    }

    atexit(CloseListeners);

    if (!SetAuthentication (numTransports, listenObjs, &authDataEntries))
    {
	fprintf (stderr, "Could not set authorization\n");
	exit (1);
    }

    InitWatchProcs (appContext);

    for (i = 0; i < numTransports; i++)
    {
	XtAppAddInput (appContext,
	    IceGetListenConnectionNumber (listenObjs[i]),
	    (XtPointer) XtInputReadMask,
	    NewConnectionXtProc, (XtPointer) listenObjs[i]);
    }

    /* the sizeof includes the \0, so we don't need to count the '=' */
    networkIds = IceComposeNetworkIdList (numTransports, listenObjs);
    XtAsprintf(&p, "%s=%s", environment_name, networkIds);
    putenv(p);

    if (cmd_line_display)
    {
	/*
	 * If a display was passed on the command line, set the DISPLAY
	 * environment in this process so all applications started by
	 * the session manager will run on the specified display.
	 */

	XtAsprintf(&p, "DISPLAY=%s", cmd_line_display);
	putenv(p);
    }

    if (verbose)
	printf ("setenv %s %s\n", environment_name, networkIds);

    create_choose_session_popup ();
    create_main_window ();
    create_client_info_popup ();
    create_save_popup ();
    create_log_popup ();


    /*
     * Initalize all lists
     */

    RunningList = ListInit();
    if(!RunningList) nomem();

    PendingList = ListInit();
    if(!PendingList) nomem();

    RestartAnywayList = ListInit();
    if(!RestartAnywayList) nomem();

    RestartImmedList = ListInit();
    if(!RestartImmedList) nomem();

    WaitForSaveDoneList = ListInit();
    if (!WaitForSaveDoneList) nomem();

    InitialSaveList = ListInit();
    if (!InitialSaveList) nomem();

    FailedSaveList = ListInit();
    if (!FailedSaveList) nomem();

    WaitForInteractList = ListInit();
    if (!WaitForInteractList) nomem();

    WaitForPhase2List = ListInit();
    if (!WaitForPhase2List) nomem();


    /*
     * Get list of session names.  If a session name was found on the
     * command line, and it is in the list of session names we got, then
     * use that session name.  If there were no session names found, then
     * use the default session name.  Otherwise, present a list of session
     * names for the user to choose from.
     */

    success = GetSessionNames (&sessionNameCount,
	&sessionNamesShort, &sessionNamesLong, &sessionsLocked);

    found_command_line_name = 0;
    if (success && session_name)
    {
	for (i = 0; i < sessionNameCount; i++)
	    if (strcmp (session_name, sessionNamesShort[i]) == 0)
	    {
		found_command_line_name = 1;

		if (sessionsLocked[i])
		{
		    fprintf (stderr, "Session '%s' is locked\n", session_name);
		    exit (1);
		}

		break;
	    }
    }

    if (!success || found_command_line_name)
    {
	FreeSessionNames (sessionNameCount,
	    sessionNamesShort, sessionNamesLong, sessionsLocked);

	if (!found_command_line_name)
	    session_name = XtNewString (DEFAULT_SESSION_NAME);

    	if (!StartSession (session_name, !found_command_line_name))
	    UnableToLockSession (session_name);
    }
    else
    {
	ChooseSession ();
    }
    

    /*
     * Main loop
     */

    XtAppMainLoop (appContext);
    exit(0);
}



static void
PropertyChangeXtHandler(Widget w, XtPointer closure, XEvent *event, 
			Boolean *continue_to_dispatch)
{
    if (w == topLevel && event->type == PropertyNotify &&
	event->xproperty.atom == wmStateAtom)
    {
	XtRemoveEventHandler (topLevel, PropertyChangeMask, False,
	    PropertyChangeXtHandler, NULL);

	/*
	 * Restart the rest of the session aware clients.
	 */

	Restart (RESTART_REST_OF_CLIENTS);


	/*
	 * Start apps that aren't session aware that were specified
	 * by the user.
	 */

	StartNonSessionAwareApps ();
    }
}



void
SetWM_DELETE_WINDOW(Widget widget, const _XtString delAction)
{
    char translation[64];

    snprintf (translation, sizeof(translation),
	      "<Message>WM_PROTOCOLS: %s", delAction);
    XtOverrideTranslations (widget, XtParseTranslationTable (translation));

    XSetWMProtocols (XtDisplay(widget), XtWindow (widget),
	&wmDeleteAtom, 1);
}



static void
GetEnvironment(void)
{
    static char	envDISPLAY[]="DISPLAY";
    static char	envSESSION_MANAGER[]="SESSION_MANAGER";
    static char	envAUDIOSERVER[]="AUDIOSERVER";
    char	*p, *temp;

    remote_allowed = 1;

    display_env = NULL;
    if((p = cmd_line_display) || (p = (char *) getenv(envDISPLAY))) {
	XtAsprintf(&display_env, "%s=%s", envDISPLAY, p);

	/*
	 * When we restart a remote client, we have to make sure the
	 * display environment we give it has the SM's hostname.
	 */

	if ((temp = strchr (p, '/')) == NULL)
	    temp = p;
	else
	    temp++;

	if (*temp != ':')
	{
	    /* we have a host name */

	    non_local_display_env = (char *) XtMalloc (
		strlen (display_env) + 1);
	    if (!non_local_display_env) nomem();

	    strcpy (non_local_display_env, display_env);
	}
	else
	{
	    char hostnamebuf[256];

	    gethostname (hostnamebuf, sizeof hostnamebuf);
	    XtAsprintf(&non_local_display_env, "%s=%s%s",
		envDISPLAY, hostnamebuf, temp);
	}
    }

    session_env = NULL;
    if((p = (char *) getenv(envSESSION_MANAGER))) {
	XtAsprintf(&session_env, "%s=%s", envSESSION_MANAGER, p);

	/*
	 * When we restart a remote client, we have to make sure the
	 * session environment does not have the SM's local connection port.
	 */

	non_local_session_env = (char *) XtMalloc (strlen (session_env) + 1);
	if (!non_local_session_env) nomem();
	strcpy (non_local_session_env, session_env);

	if ((temp = Strstr (non_local_session_env, "local/")) != NULL)
	{
	    char *delim = strchr (temp, ',');
	    if (delim == NULL)
	    {
		if (temp == non_local_session_env +
		    strlen (envSESSION_MANAGER) + 1)
		{
		    *temp = '\0';
		    remote_allowed = 0;
		}
		else
		    *(temp - 1) = '\0';
	    }
	    else
	    {
		int bytes = strlen (delim + 1);
		memmove (temp, delim + 1, bytes);
		*(temp + bytes) = '\0';
	    }
	}
    }

    audio_env = NULL;
    if((p = (char *) getenv(envAUDIOSERVER))) {
	XtAsprintf(&audio_env, "%s=%s", envAUDIOSERVER, p);
    }
}



Status
StartSession(char *name, Bool use_default)
{
    int database_read = 0;
    Dimension width;
    char title[256];


    /*
     * If we're not using the default session, lock it.
     * If using the default session, it will be locked as
     * soon as the user assigns the session a name.
     */

    if (!use_default && !LockSession (name, True))
	return (0);


    /*
     * Get important environment variables.
     */

    GetEnvironment ();


    /*
     * Set the main window's title to the session name.
     */

    snprintf (title, sizeof(title), "xsm: %s", name);

    XtVaSetValues (topLevel,
	XtNtitle, title,		/* session name */
	NULL);

    XtRealizeWidget (topLevel);


    /*
     * Set WM_DELETE_WINDOW support on main window.  If the user tries
     * to delete the main window, the shutdown prompt will come up.
     */

    SetWM_DELETE_WINDOW (topLevel, "DelMainWinAction()");


    /*
     * Read the session save file.  Make sure the session manager
     * has an SM_CLIENT_ID, so that other managers (like the WM) can
     * identify it.
     */

    set_session_save_file_name (name);

    if (use_default)
	need_to_name_session = True;
    else
    {
	database_read = ReadSave (name, &sm_id);
	need_to_name_session = !database_read;
    }

    if (!sm_id)
    {
	sm_id = SmsGenerateClientID (NULL);
	if (!sm_id) return (0);
    }
    XChangeProperty (XtDisplay (topLevel), XtWindow (topLevel),
	XInternAtom (XtDisplay (topLevel), "SM_CLIENT_ID", False),
	XA_STRING, 8, PropModeReplace,
	(unsigned char *) sm_id, strlen (sm_id));


    /*
     * Adjust some label widths
     */

    XtVaGetValues (clientInfoButton,
	XtNwidth, &width,
	NULL);

    XtVaSetValues (checkPointButton,
	XtNwidth, width,
	NULL);

    XtVaGetValues (logButton,
	XtNwidth, &width,
	NULL);

    XtVaSetValues (shutdownButton,
	XtNwidth, width,
	NULL);
    

    XtMapWidget (topLevel);


    if (!database_read)
    {
	/*
	 * Start default apps (e.g. twm, smproxy)
	 */

	StartDefaultApps ();
    }
    else
    {
	/*
	 * Restart window manager first.  When the session manager
	 * gets a WM_STATE stored on its top level window, we know
	 * the window manager is running.  At that time, we can start
	 * the rest of the applications.
	 */

	XtAddEventHandler (topLevel, PropertyChangeMask, False,
	    PropertyChangeXtHandler, NULL);

	if (!Restart (RESTART_MANAGERS))
	{
	    XtRemoveEventHandler (topLevel, PropertyChangeMask, False,
	        PropertyChangeXtHandler, NULL);

	    /*
	     * Restart the rest of the session aware clients.
	     */

	    Restart (RESTART_REST_OF_CLIENTS);

	    /*
	     * Start apps that aren't session aware that were specified
	     * by the user.
	     */
	    
	    StartNonSessionAwareApps ();
	}
    }

    return (1);
}



void
EndSession(int status)
{
    if (verbose)
	printf ("\nSESSION MANAGER GOING AWAY!\n");

    FreeAuthenticationData (numTransports, authDataEntries);

    if (session_name)
    {
	UnlockSession (session_name);
	XtFree (session_name);
    }

    if (display_env)
	XtFree (display_env);
    if (session_env)
	XtFree (session_env);
    if (cmd_line_display)
	XtFree (cmd_line_display);
    if (non_local_display_env)
	XtFree (non_local_display_env);
    if (non_local_session_env)
	XtFree (non_local_session_env);
    if (audio_env)
	XtFree (audio_env);
    if (networkIds)
	free (networkIds);

    exit (status);
}



void
FreeClient(ClientRec *client, Bool freeProps)
{
    if (freeProps)
    {
	List *pl;

	for (pl = ListFirst (client->props); pl; pl = ListNext (pl))
	    FreeProp ((Prop *) pl->thing);

	ListFreeAll (client->props);
    }

    if (client->clientId)
	free (client->clientId);		/* malloc'd by SMlib */
    if (client->clientHostname)
	free (client->clientHostname);		/* malloc'd by SMlib */

    if (client->discardCommand)
	XtFree (client->discardCommand);
    if (client->saveDiscardCommand)
	XtFree (client->saveDiscardCommand);

    XtFree ((char *) client);
}



/*
 * Session Manager callbacks
 */

static Status
RegisterClientProc(SmsConn smsConn, SmPointer managerData, char *previousId)
{
    ClientRec	*client = (ClientRec *) managerData;
    char 	*id;
    List	*cl;
    int		send_save;

    if (verbose)
    {
	printf (
	"On IceConn fd = %d, received REGISTER CLIENT [Previous Id = %s]\n",
	IceConnectionNumber (client->ice_conn),
	previousId ? previousId : "NULL");
	printf ("\n");
    }

    if (!previousId)
    {
	id = SmsGenerateClientID (smsConn);
	send_save = 1;
    }
    else
    {
	int found_match = 0;
	send_save = 1;

	for (cl = ListFirst (PendingList); cl; cl = ListNext (cl))
	{
	    PendingClient *pendClient = (PendingClient *) cl->thing;

	    if (!strcmp (pendClient->clientId, previousId))
	    {
		SetInitialProperties (client, pendClient->props);
		XtFree (pendClient->clientId);
		XtFree (pendClient->clientHostname);
		XtFree ((char *) pendClient);
		ListFreeOne (cl);
		found_match = 1;
		send_save = 0;
		break;
	    }
	}

	if (!found_match)
	{
	    for (cl = ListFirst (RestartAnywayList); cl; cl = ListNext (cl))
	    {
		ClientRec *rClient = (ClientRec *) cl->thing;

		if (!strcmp (rClient->clientId, previousId))
		{
		    SetInitialProperties (client, rClient->props);
		    FreeClient (rClient, False /* don't free props */);
		    ListFreeOne (cl);
		    found_match = 1;
		    send_save = 0;
		    break;
		}
	    }
	}

	if (!found_match)
	{
	    for (cl = ListFirst (RestartImmedList); cl; cl = ListNext (cl))
	    {
		ClientRec *rClient = (ClientRec *) cl->thing;

		if (!strcmp (rClient->clientId, previousId))
		{
		    SetInitialProperties (client, rClient->props);
		    FreeClient (rClient, False /* don't free props */);
		    ListFreeOne (cl);
		    found_match = 1;
		    send_save = 0;
		    break;
		}
	    }
	}

	if (!found_match)
	{
	    /*
	     * previous-id was bogus: return bad status and the client
	     * should re-register with a NULL previous-id
	     */

	    free (previousId);
	    return (0);
	}
	else
	{
	    id = previousId;
	}
    }

    SmsRegisterClientReply (smsConn, id);

    if (verbose)
    {
	printf (
	"On IceConn fd = %d, sent REGISTER CLIENT REPLY [Client Id = %s]\n",
	IceConnectionNumber (client->ice_conn), id);
	printf ("\n");
    }

    client->clientId = id;
    client->clientHostname = SmsClientHostName (smsConn);
    client->restarted = (previousId != NULL);

    if (send_save)
    {
	SmsSaveYourself (smsConn, SmSaveLocal,
	    False, SmInteractStyleNone, False);

	ListAddLast (InitialSaveList, (char *) client);
    }
    else if (client_info_visible)
    {
	/* We already have all required client info */

	UpdateClientList ();
	XawListHighlight (clientListWidget, current_client_selected);
    }

    return (1);
}



static Bool
OkToEnterInteractPhase(void)
{
    return ((ListCount (WaitForInteractList) +
	ListCount (WaitForPhase2List)) == ListCount (WaitForSaveDoneList));
}



static void
InteractRequestProc(SmsConn smsConn, SmPointer managerData, int dialogType)
{
    ClientRec	*client = (ClientRec *) managerData;

    if (verbose)
    {
	printf ("Client Id = %s, received INTERACT REQUEST [Dialog Type = ",
		client->clientId);
	if (dialogType == SmDialogError)
	    printf ("Error]\n");
	else if (dialogType == SmDialogNormal)
	    printf ("Normal]\n");
	else
	    printf ("Error in SMlib: should have checked for bad value]\n");
    }

    ListAddLast (WaitForInteractList, (char *) client);

    if (OkToEnterInteractPhase ())
    {
	LetClientInteract (ListFirst (WaitForInteractList));
    }
}



static void
InteractDoneProc(SmsConn smsConn, SmPointer managerData, Bool cancelShutdown)
{
    ClientRec	*client = (ClientRec *) managerData;
    List	*cl;

    if (verbose)
    {
	printf (
	"Client Id = %s, received INTERACT DONE [Cancel Shutdown = %s]\n",
	client->clientId, cancelShutdown ? "True" : "False");
    }

    if (cancelShutdown)
    {
	ListFreeAllButHead (WaitForInteractList);
	ListFreeAllButHead (WaitForPhase2List);
    }

    if (cancelShutdown)
    {
	if (shutdownCancelled)
	{
	    /* Shutdown was already cancelled */
	    return;
	}

	shutdownCancelled = True;

	for (cl = ListFirst (RunningList); cl; cl = ListNext (cl))
	{
	    client = (ClientRec *) cl->thing;

	    SmsShutdownCancelled (client->smsConn);

	    if (verbose) 
	    {
		printf ("Client Id = %s, sent SHUTDOWN CANCELLED\n",
			client->clientId);
	    }
	}
    }
    else
    {
	if ((cl = ListFirst (WaitForInteractList)) != NULL)
	{
	    LetClientInteract (cl);
	}
	else
	{
	    if (verbose)
	    {
		printf ("\n");
		printf ("Done interacting with all clients.\n");
		printf ("\n");
	    }

	    if (ListCount (WaitForPhase2List) > 0)
	    {
		StartPhase2 ();
	    }
	}
    }
}



static void
SaveYourselfReqProc(SmsConn smsConn, SmPointer managerData, int saveType,
    Bool shutdown, int interactStyle, Bool fast, Bool global)
{
    if (verbose) 
	printf("SAVE YOURSELF REQUEST not supported!\n");
}



static Bool
OkToEnterPhase2(void)

{
    return (ListCount (WaitForPhase2List) == ListCount (WaitForSaveDoneList));
}



static void
SaveYourselfPhase2ReqProc(SmsConn smsConn, SmPointer managerData)
{
    ClientRec	*client = (ClientRec *) managerData;

    if (verbose)
    {
	printf ("Client Id = %s, received SAVE YOURSELF PHASE 2 REQUEST\n",
	    client->clientId);
    }

    if (!saveInProgress)
    {
	/*
	 * If we are not in the middle of a checkpoint (ie. we just
	 * started the client and sent the initial save yourself), just
	 * send the save yourself phase2 now.
	 */
	 
	SmsSaveYourselfPhase2 (client->smsConn);
    }
    else
    {
	ListAddLast (WaitForPhase2List, (char *) client);

	if (ListCount (WaitForInteractList) > 0 && OkToEnterInteractPhase ())
	{
	    LetClientInteract (ListFirst (WaitForInteractList));
	}
	else if (OkToEnterPhase2 ())
	{
	    StartPhase2 ();
	}
    }
}



static void
SaveYourselfDoneProc(SmsConn smsConn, SmPointer managerData, Bool success)
{
    ClientRec	*client = (ClientRec *) managerData;

    if (verbose) 
    {
	printf("Client Id = %s, received SAVE YOURSELF DONE [Success = %s]\n",
	       client->clientId, success ? "True" : "False");
    }

    if (!ListSearchAndFreeOne (WaitForSaveDoneList, (char *) client))
    {
	if (ListSearchAndFreeOne (InitialSaveList, (char *) client))
	    SmsSaveComplete (client->smsConn);
	return;
    }

    if (!success)
    {
	ListAddLast (FailedSaveList, (char *) client);
    }

    if (ListCount (WaitForSaveDoneList) == 0)
    {
	if (ListCount (FailedSaveList) > 0 && !checkpoint_from_signal)
	    PopupBadSave ();
	else
	    FinishUpSave ();
    }
    else if (ListCount (WaitForInteractList) > 0 && OkToEnterInteractPhase ())
    {
	LetClientInteract (ListFirst (WaitForInteractList));
    }
    else if (ListCount (WaitForPhase2List) > 0 && OkToEnterPhase2 ())
    {
	StartPhase2 ();
    }
}



void
CloseDownClient(ClientRec *client)
{
    int index_deleted = 0;

    if (verbose) {
	printf ("ICE Connection closed, IceConn fd = %d\n",
		IceConnectionNumber (client->ice_conn));
	printf ("\n");
    }

    SmsCleanUp (client->smsConn);
    IceSetShutdownNegotiation (client->ice_conn, False);
    IceCloseConnection (client->ice_conn);

    client->ice_conn = NULL;
    client->smsConn = NULL;

    if (!shutdownInProgress && client_info_visible)
    {
	for (index_deleted = 0;
	    index_deleted < numClientListNames; index_deleted++)
	{
	    if (clientListRecs[index_deleted] == client)
		break;
	}
    }

    ListSearchAndFreeOne (RunningList, (char *) client);

    if (saveInProgress)
    {
	Status delStatus = ListSearchAndFreeOne (
	    WaitForSaveDoneList, (char *) client);

	if (delStatus)
	{
	    ListAddLast (FailedSaveList, (char *) client);
	    client->freeAfterBadSavePopup = True;
	}

	ListSearchAndFreeOne (WaitForInteractList, (char *) client);
	ListSearchAndFreeOne (WaitForPhase2List, (char *) client);

	if (delStatus && ListCount (WaitForSaveDoneList) == 0)
	{
	    if (ListCount (FailedSaveList) > 0 && !checkpoint_from_signal)
		PopupBadSave ();
	    else
		FinishUpSave ();
	}
	else if (ListCount (WaitForInteractList) > 0 &&
	    OkToEnterInteractPhase ())
	{
	    LetClientInteract (ListFirst (WaitForInteractList));
	}
	else if (!phase2InProgress &&
	    ListCount (WaitForPhase2List) > 0 && OkToEnterPhase2 ())
	{
	    StartPhase2 ();
	}
    }

    if (client->restartHint == SmRestartImmediately && !shutdownInProgress)
    {
	Clone (client, True /* use saved state */);

	ListAddLast (RestartImmedList, (char *) client);
    }
    else if (client->restartHint == SmRestartAnyway)
    {
	ListAddLast (RestartAnywayList, (char *) client);
    }
    else if (!client->freeAfterBadSavePopup)
    {
	FreeClient (client, True /* free props */);
    }

    if (shutdownInProgress)
    {
	if (ListCount (RunningList) == 0)
	    EndSession (0);
    }
    else if (client_info_visible)
    {
	UpdateClientList ();

	if (current_client_selected == index_deleted)
	{
	    if (current_client_selected == numClientListNames)
		current_client_selected--;

	    if (current_client_selected >= 0)
	    {
		XawListHighlight (clientListWidget, current_client_selected);
		ShowHint (clientListRecs[current_client_selected]);
		if (client_prop_visible)
		{
		    DisplayProps (clientListRecs[current_client_selected]);
		}
	    }
	}
	else
	{
	    if (index_deleted < current_client_selected)
		current_client_selected--;
	    XawListHighlight (clientListWidget, current_client_selected);
	}
    }
}




static void
CloseConnectionProc(SmsConn smsConn, SmPointer managerData, 
		    int count, char **reasonMsgs)
{
    ClientRec	*client = (ClientRec *) managerData;

    if (verbose)
    {
	int i;

	printf ("Client Id = %s, received CONNECTION CLOSED\n",
	    client->clientId);

	for (i = 0; i < count; i++)
	    printf ("   Reason string %d: %s\n", i + 1, reasonMsgs[i]);
	printf ("\n");
    }

    SmFreeReasons (count, reasonMsgs);

    CloseDownClient (client);
}



static Status
NewClientProc(SmsConn smsConn, SmPointer managerData, unsigned long *maskRet, 
	      SmsCallbacks *callbacksRet, char **failureReasonRet)
{
    ClientRec *newClient = (ClientRec *) XtMalloc (sizeof (ClientRec));

    *maskRet = 0;

    if (!newClient)
    {
	const char *str = "Memory allocation failed";

	if ((*failureReasonRet = (char *) XtMalloc (strlen (str) + 1)) != NULL)
	    strcpy (*failureReasonRet, str);

	return (0);
    }

    newClient->smsConn = smsConn;
    newClient->ice_conn = SmsGetIceConnection (smsConn);
    newClient->clientId = NULL;
    newClient->clientHostname = NULL;
    newClient->restarted = False; /* wait till RegisterClient for true value */
    newClient->userIssuedCheckpoint = False;
    newClient->receivedDiscardCommand = False;
    newClient->freeAfterBadSavePopup = False;
    newClient->props = ListInit ();
    newClient->discardCommand = NULL;
    newClient->saveDiscardCommand = NULL;
    newClient->restartHint = SmRestartIfRunning;

    ListAddLast (RunningList, (char *) newClient);

    if (verbose) {
	printf("On IceConn fd = %d, client set up session mngmt protocol\n\n",
	       IceConnectionNumber (newClient->ice_conn));
    }

    /*
     * Set up session manager callbacks.
     */

    *maskRet |= SmsRegisterClientProcMask;
    callbacksRet->register_client.callback 	= RegisterClientProc;
    callbacksRet->register_client.manager_data  = (SmPointer) newClient;

    *maskRet |= SmsInteractRequestProcMask;
    callbacksRet->interact_request.callback 	= InteractRequestProc;
    callbacksRet->interact_request.manager_data = (SmPointer) newClient;

    *maskRet |= SmsInteractDoneProcMask;
    callbacksRet->interact_done.callback	= InteractDoneProc;
    callbacksRet->interact_done.manager_data    = (SmPointer) newClient;

    *maskRet |= SmsSaveYourselfRequestProcMask;
    callbacksRet->save_yourself_request.callback     = SaveYourselfReqProc;
    callbacksRet->save_yourself_request.manager_data = (SmPointer) newClient;

    *maskRet |= SmsSaveYourselfP2RequestProcMask;
    callbacksRet->save_yourself_phase2_request.callback =
	SaveYourselfPhase2ReqProc;
    callbacksRet->save_yourself_phase2_request.manager_data =
	(SmPointer) newClient;

    *maskRet |= SmsSaveYourselfDoneProcMask;
    callbacksRet->save_yourself_done.callback 	   = SaveYourselfDoneProc;
    callbacksRet->save_yourself_done.manager_data  = (SmPointer) newClient;

    *maskRet |= SmsCloseConnectionProcMask;
    callbacksRet->close_connection.callback 	 = CloseConnectionProc;
    callbacksRet->close_connection.manager_data  = (SmPointer) newClient;

    *maskRet |= SmsSetPropertiesProcMask;
    callbacksRet->set_properties.callback 	= SetPropertiesProc;
    callbacksRet->set_properties.manager_data   = (SmPointer) newClient;

    *maskRet |= SmsDeletePropertiesProcMask;
    callbacksRet->delete_properties.callback	= DeletePropertiesProc;
    callbacksRet->delete_properties.manager_data   = (SmPointer) newClient;

    *maskRet |= SmsGetPropertiesProcMask;
    callbacksRet->get_properties.callback	= GetPropertiesProc;
    callbacksRet->get_properties.manager_data   = (SmPointer) newClient;

    return (1);
}



/*
 * Xt callback invoked when a client attempts to connect.
 */

static void
NewConnectionXtProc(XtPointer client_data, int *source, XtInputId *id)
{
    IceConn 	ice_conn;
    char	*connstr;
    IceAcceptStatus status;

    if (shutdownInProgress)
    {
	/*
	 * Don't accept new connections if we are in the middle
	 * of a shutdown.
	 */

	return;
    }

    ice_conn = IceAcceptConnection((IceListenObj) client_data, &status);
    if (! ice_conn) {
	if (verbose)
	    printf ("IceAcceptConnection failed\n");
    } else {
	IceConnectStatus cstatus;

	while ((cstatus = IceConnectionStatus (ice_conn))==IceConnectPending) {
	    XtAppProcessEvent (appContext, XtIMAll);
	}

	if (cstatus == IceConnectAccepted) {
	    if (verbose) {
		printf ("ICE Connection opened by client, IceConn fd = %d, ",
			IceConnectionNumber (ice_conn));
		connstr = IceConnectionString (ice_conn);
		printf ("Accept at networkId %s\n", connstr);
		free (connstr);
		printf ("\n");
	    }
	} else {
	    if (verbose)
	    {
		if (cstatus == IceConnectIOError)
		    printf ("IO error opening ICE Connection!\n");
		else
		    printf ("ICE Connection rejected!\n");
	    }

	    IceCloseConnection (ice_conn);
	}
    }
}



void
SetAllSensitive(Bool on)
{
    XtSetSensitive (mainWindow, on);
    SetSaveSensitivity (on);
    XtSetSensitive (clientInfoPopup, on);
    XtSetSensitive (clientPropPopup, on);

    if (on && current_client_selected >= 0)
	XawListHighlight (clientListWidget, current_client_selected);
}



/*
 * The real way to handle IO errors is to check the return status
 * of IceProcessMessages.  xsm properly does this.
 *
 * Unfortunately, a design flaw exists in the ICE library in which
 * a default IO error handler is invoked if no IO error handler is
 * installed.  This default handler exits.  We must avoid this.
 *
 * To get around this problem, we install an IO error handler that
 * does a little magic.  Since a previous IO handler might have been
 * installed, when we install our IO error handler, we do a little
 * trick to get both the previous IO error handler and the default
 * IO error handler.  When our IO error handler is called, if the
 * previous handler is not the default handler, we call it.  This
 * way, everyone's IO error handler gets called except the stupid
 * default one which does an exit!
 */

static IceIOErrorHandler prev_handler;

static void
MyIoErrorHandler(IceConn ice_conn)
{
    if (prev_handler)
	(*prev_handler) (ice_conn);
}    

static void
InstallIOErrorHandler(void)

{
    IceIOErrorHandler default_handler;

    prev_handler = IceSetIOErrorHandler (NULL);
    default_handler = IceSetIOErrorHandler (MyIoErrorHandler);
    if (prev_handler == default_handler)
	prev_handler = NULL;
}

static void
CloseListeners(void)

{
    IceFreeListenObjs (numTransports, listenObjs);
}

