/* $XConsortium: WmXSMP.c /main/12 1996/05/17 12:54:14 rswiston $ */
/*
 * (c) Copyright 1996 Digital Equipment Corporation.
 * (c) Copyright 1996 Hewlett-Packard Company.
 * (c) Copyright 1996 International Business Machines Corp.
 * (c) Copyright 1996 Sun Microsystems, Inc.
 * (c) Copyright 1996 Novell, Inc. 
 * (c) Copyright 1996 FUJITSU LIMITED.
 * (c) Copyright 1996 Hitachi.
 */

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <sys/param.h>
#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/Xatom.h>
#include <X11/SM/SM.h>
#include <Xm/XmP.h>
#include "WmGlobal.h"
#include "WmXSMP.h"
#ifdef WSM
# include "WmWrkspace.h"
# include <Dt/Session.h>
#endif

typedef struct _ProxyClientInfo
{
    int screen;
    char *wmCommand;
    char *wmClientMachine;
    char *clientID;
} ProxyClientInfo;

#define RESTORE_RESOURCE(pCD, resFlag) \
	((pCD)->ignoreWMSaveHints || !((pCD)->wmSaveHintFlags & (resFlag)))
#define SAVE_RESOURCE(pCD, resFlag) RESTORE_RESOURCE(pCD, resFlag)

#define MAX_RESOURCE_LEN 1024

#ifdef WSM
static char *dtwmFileName = "dtwm.db";
#else
static char *dtwmFileName = ".mwmclientdb";
# define EXTRA_FN_CHARS 20
#endif

/* Fully-qualified resource names/classes. */
static char *xPositionStr = "%s.position.x";
static char *yPositionStr = "%s.position.y";
static char *widthSizeStr = "%s.size.width";
static char *heightSizeStr = "%s.size.height";
static char *initialStateStr = "%s.initialState";
static char *wmCommandStr = "%s.wmCommand";
static char *wmClientMachineStr = "%s.wmClientMachine";
static char *screenStr = "%s.screen";
#ifdef WSM
static char *workspacesStr = "%s.workspaces";
static char *iconXPosStr = "%s.iconPos.x.%s";
static char *iconYPosStr = "%s.iconPos.y.%s";
#else
static char *iconXPosStr = "%s.iconPos.x";
static char *iconYPosStr = "%s.iconPos.y";
#endif

/* Header for private database. */
static char *dbHeader = "\
! %s\n\
!\n\
.version: %s\n\
.dtwmID: %s\n";

/* Format for client entries in database. */
static char *dbClientFormat = "\
!\n\
%s.%s: %s\n\
!\n";
static char *intArg = ": %d\n";
static char *strArg = ": %s\n";
static char *normalStateStr = "NormalState";
static char *iconicStateStr = "IconicState";

static char *XSMPClientStr = "Client";
static char *proxyClientStr = "ProxyClient";

#ifndef WSM
static char *dbFileArgStr = "-session";
#endif

/* Flag to tell us how to treat ProxyClient info. */
static Boolean smClientDBCheckpointed = False;

/*
 *  Prototypes
 */
/* Session mgmt callbacks. */
static void smSaveYourselfCallback(Widget, XtPointer, XtPointer);
static void smDieCallback(Widget, XtPointer, XtPointer);

/* Build client database file name. */
static void buildDBFileName(char [MAXPATHLEN], Boolean);
#ifndef WSM
/*
 *Get clientDB name according to argv; set according to dbFileName.
 */
static void getClientDBName(void);
static void setClientDBName(void);
static char **getNewRestartCmd(void);
static void freeNewRestartCmd(char **);
#endif /* ! WSM */

#ifdef WSM
/* Get string of client's workspaces. */
static char *getClientWorkspaces(ClientData *);
#endif

/* List-of-clients utilities. */
static Boolean addClientToList(ClientData ***, int *, ClientData *);
static int clientWorkspaceCompare(const void *, const void *);

/* XSMP/Proxy functions to save/restore resources. */
static char *getClientResource(char *, char *);
static char *getXSMPResource(ClientData *, int, char *);
static void getClientGeometry(ClientData *, int *, int *,
			      unsigned int *, unsigned int *);
static Boolean getProxyClientInfo(ClientData *, ProxyClientInfo *);
static Bool cmpProxyClientProc(XrmDatabase *, XrmBindingList,
			       XrmQuarkList, XrmRepresentation *,
			       XrmValue *, XPointer);
static char *findProxyClientID(ClientData *);
static Boolean findXSMPClientDBMatch(ClientData *, char **);
static Boolean findProxyClientDBMatch(ClientData *, char **);
static Boolean saveXSMPClient(FILE *, ClientData *);
static Boolean saveProxyClient(FILE *, ClientData *, int);
static void dbRemoveProxyClientEntry(char *);

static void
smSaveYourselfCallback(Widget w, XtPointer clientData, XtPointer callData)
{
    XtCheckpointToken cpToken = (XtCheckpointToken)callData;
    XrmDatabase newClientDB;
    int scr;
    static Boolean firstTime = True;

    /*
     *  This callback will be called on connection to the Session Manager.
     *  At that time, we don't want to save any state, and we don't
     *  want to request the second phase.
     */
    if (firstTime)
    {
	firstTime = False;
	return;
    }

    /* Only respond to Local and Both save requests. */
    if ((cpToken->save_type != SmSaveLocal) &&
	(cpToken->save_type != SmSaveBoth))
	return;

    if (cpToken->shutdown &&
	(cpToken->cancel_shutdown ||
	 cpToken->request_cancel ||
	 !cpToken->save_success))
	return;  /* Return, maintaining current state */

    /* If first phase, request notification when all other clients saved. */
    if (cpToken->phase == 1)
    {
	cpToken->request_next_phase = True;
	return;
    }

#ifdef WSM
    /* Second phase: all other clients saved; now I can save myself. */
    /* Copied from WmEvent.c. */
    for (scr = 0; scr < wmGD.numScreens; scr++)
    {
	if (wmGD.Screens[scr].managed)
	{
	    /*
	     * Write out current workspace, frontpanel 
	     * position and iconbox position and size.
	     */
	    SaveResources(&wmGD.Screens[scr]);
	}
    }
#endif

    /*
     *  NEW FOR SESSION MANAGEMENT: Write private client resource database.
     *  Destroy old client database and save new one.
     */
    if ((newClientDB = SaveClientResourceDB())
	!= (XrmDatabase)NULL)
    {
	if (wmGD.clientResourceDB != (XrmDatabase)NULL)
	    XrmDestroyDatabase(wmGD.clientResourceDB);
	wmGD.clientResourceDB = newClientDB;
	smClientDBCheckpointed = True;

#ifndef WSM
	/* Set new session properties if wmGD.dbFileName is valid. */
	if (wmGD.dbFileName != (char *)NULL)
	{
	    char **newRestartCmd, **ptr;
	    char *newDiscardCmd[4];
	    Arg args[10];
	    int nargs;

	    newDiscardCmd[0] = "rm";
	    newDiscardCmd[1] = "-f";
	    newDiscardCmd[2] = wmGD.dbFileName;
	    newDiscardCmd[3] = (char *)NULL;

	    newRestartCmd = getNewRestartCmd();

	    nargs = 0;
	    XtSetArg(args[nargs], XtNrestartCommand, newRestartCmd); nargs++;
	    XtSetArg(args[nargs], XtNdiscardCommand, newDiscardCmd); nargs++;
	    XtSetValues(wmGD.topLevelW, args, nargs);

	    freeNewRestartCmd(newRestartCmd);
	}
#endif /* ! WSM */
    }
}

static void
smDieCallback(Widget w, XtPointer clientData, XtPointer callData)
{
    /* We assume we've saved our state by the time this is called. */
    ExitWM(0);
}

static void
buildDBFileName(char fileNameBuf[MAXPATHLEN], Boolean doingSave)
{
#ifdef WSM

    char *savePath = (char *)NULL;

    fileNameBuf[0] = '\0';
    if (doingSave)
    {
	char *saveFile = (char *)NULL;
	char *ptr;

	if (DtSessionSavePath(wmGD.topLevelW, &savePath, &saveFile))
	{
	    XtFree(saveFile);

	    if ((ptr = strrchr(savePath, '/')) != (char *)NULL)
		*ptr = '\0';

	    if (strlen(savePath) + strlen(dtwmFileName) + 2 < MAXPATHLEN)
		sprintf(fileNameBuf, "%s/%s", savePath, dtwmFileName);

	    XtFree(savePath);
	}
    }
    else
    {
	if (DtSessionRestorePath(wmGD.topLevelW, &savePath, dtwmFileName))
	{
	    if ((int)strlen(savePath) < MAXPATHLEN)
		strcpy(fileNameBuf, savePath);

	    XtFree(savePath);
	}
    }

    if (fileNameBuf[0] == '\0')
	strcpy(fileNameBuf, dtwmFileName);

#else

    strcpy(fileNameBuf, (wmGD.dbFileName == (char *)NULL) ?
	   dtwmFileName : wmGD.dbFileName);

#endif
}

#ifndef WSM

/*
 *  See if dbFileArgStr specified on command line.  Save subsequent arg;
 *  if not, see if resource set; if not, put files in user's home directory.
 *  NOTE: we allocate extra space for the filename so we can append numbers
 *  without reallocating in setClientDBName.
 */
static void
getClientDBName(void)
{
    char **argP;

    /* See if DB filename specified on command line. */
    wmGD.dbFileName = (char *)NULL;

    if (wmGD.argv != (char **)NULL)
    {
	for (argP = wmGD.argv; *argP != (char *)NULL; argP++)
	{
	    if (strcmp(*argP, dbFileArgStr) == 0)
	    {
		if (*(++argP) != (char *)NULL)
		{
		    if ((wmGD.dbFileName =
			 (char *)XtMalloc((strlen(*argP) + 1 +
					   EXTRA_FN_CHARS) *
					  sizeof(char)))
			!= (char *)NULL)
			strcpy(wmGD.dbFileName, *argP);
		}
		break;
	    }
	}
    }

    /* Check resource if necessary. */
    if (wmGD.dbFileName == (char *)NULL)
    {
	if (wmGD.sessionClientDB != (String)NULL)
	{
	    if ((wmGD.dbFileName =
		 (char *)XtMalloc((strlen(wmGD.sessionClientDB) + 1 +
				   EXTRA_FN_CHARS) *
				  sizeof(char)))
		!= (char *)NULL)
		strcpy(wmGD.dbFileName, wmGD.sessionClientDB);
	}
    }

    if (wmGD.dbFileName == (char *)NULL)
    {
	char *homeDir = XmeGetHomeDirName();

	if ((wmGD.dbFileName =
	     (char *)XtMalloc((strlen(homeDir) + strlen(dtwmFileName) + 2 +
			       EXTRA_FN_CHARS) * sizeof(char)))
	    != (char *)NULL)
	    sprintf(wmGD.dbFileName, "%s/%s", homeDir, dtwmFileName);
    }
}

/*
 *  See comments above in getClientDBName.
 */
static void
setClientDBName(void)
{
    char *ptr;

    if (wmGD.dbFileName == (char *)NULL)
	return;

    /* Change trailing ".<number>" to ".<number+1>" */
    if ((ptr = strrchr(wmGD.dbFileName, '.')) != (char *)NULL)
    {
	char *p1;

	for (p1 = ++ptr; *p1 != '\0'; p1++)
	{
	    if (!isdigit(*p1))
		break;
	}

	if (*p1 == '\0')
	{
	    int numSuffix;

	    numSuffix = atoi(ptr) + 1;
	    sprintf(ptr, "%d", numSuffix);

	    /* Success!  We're all done here. */
	    return;
	}
    }

    /* Otherwise, append ".0" to filename. */
    strcat(wmGD.dbFileName, ".0");
}

static char **
getNewRestartCmd(void)
{
    char **argP;
    int argc, i;
    int fileArgIndex = -1;
    Arg args[10];
    int nargs;
    char **restartCmd;
    char **newRestartCmd;

    nargs = 0;
    XtSetArg(args[nargs], XtNrestartCommand, &restartCmd); nargs++;
    XtGetValues(wmGD.topLevelW, args, nargs);

    if (restartCmd == (char **)NULL)
	return (char **)NULL;

    for (argc = 0, argP = restartCmd; *argP != (char *)NULL; argP++, argc++)
    {
	if (strcmp(*argP, dbFileArgStr) == 0)
	{
	    if (*(++argP) == (char *)NULL)
		break;

	    fileArgIndex = argc++; /* Point at dbFileArgStr, not filename */
	}
    }

    if (fileArgIndex < 0)
    {
	fileArgIndex = argc;
	argc += 2;
    }

    if ((newRestartCmd = (char **)XtMalloc((argc + 1) * sizeof(char *)))
	== (char **)NULL)
	return (char **)NULL;

    for (i = 0; i < argc; i++)
    {
	if (i != fileArgIndex)
	{
	    newRestartCmd[i] = XtNewString(restartCmd[i]);
	}
	else
	{
	    newRestartCmd[i++] = XtNewString(dbFileArgStr);
	    newRestartCmd[i] = XtNewString(wmGD.dbFileName);
	}
    }
    newRestartCmd[i] = (char *)NULL;

    return newRestartCmd;
}

static void
freeNewRestartCmd(char **restartCmd)
{
    while (*restartCmd != (char *)NULL)
	XtFree(*(restartCmd++));

    XtFree((char *)restartCmd);
}

#endif /* ! WSM */

#ifdef WSM

static char *
getClientWorkspaces(ClientData *pCD)
{
    WmScreenData *pSD = pCD->pSD;
    WmWorkspaceData *pWS;

    /* Should we use _DtWmParseMakeQuotedString() when looking at */
    /* the name of the workspace, as is done in WmWrkspace.c? */

    /* Easy but slow way to do this would be to use XGetAtomName(). */
    /* To avoid XServer round trips (and to weed out invalid WS names) */
    /* we look through workspaces attached to this screen for ID matches. */
    char *cwsP, *tmpP, *wsNameP;
    int pLen = 0;
    int i;

    for (i = 0; i < pCD->numInhabited; i++)
    {
	if ((pWS = GetWorkspaceData(pSD, pCD->pWsList[i].wsID))
	    != (WmWorkspaceData *)NULL)
	{
	    wsNameP = pWS->name;
	    if (pLen == 0)
	    {
		pLen = strlen(wsNameP) + 1;  /* 1 for null termination */
		if ((cwsP = (char *)XtMalloc(pLen * sizeof(char)))
		    == (char *)NULL)
		    return (char *)NULL;

		strcpy(cwsP, wsNameP);
	    }
	    else
	    {
		pLen += strlen(wsNameP) + 1;  /* 1 for space */
		if ((tmpP = (char *)XtRealloc(cwsP, pLen * sizeof(char)))
		    == (char *)NULL)
		{
		    XtFree((char *)cwsP);
		    return (char *)NULL;
		}
		cwsP = tmpP;
		strcat(cwsP, " ");
		strcat(cwsP, wsNameP);
	    }
	}
    }

    return cwsP;
}

#endif /* WSM */

static Boolean
addClientToList(ClientData ***cdList, int *nClients, ClientData *pCD)
{
    ClientData **newPtr = (ClientData **)
	XtRealloc((char *)*cdList, (*nClients + 1) * sizeof(ClientData *));

    if (newPtr == (ClientData **)NULL)
    {
	if (*cdList != (ClientData **)NULL)
	    XtFree((char *)*cdList);
	return False;
    }

    *cdList = newPtr;
    newPtr[*nClients] = pCD;
    (*nClients)++;

    return True;
}

static int
clientWorkspaceCompare(const void *ppCD1, const void *ppCD2)
{
    ClientData *pCD1 = *(ClientData **)ppCD1;
    ClientData *pCD2 = *(ClientData **)ppCD2;
    int screenDiff;

    /* Sort first by screen. */
    if ((screenDiff = pCD1->pSD->screen - pCD2->pSD->screen) != 0)
	return screenDiff;

#ifdef WSM

    /* If same screen, sort by workspace id. */
    /* How do we handle clients that live in more than one workspace? */
    /* For now, pick the "current" one - if not in active workspace, */
    /* this will simply be the first one in the client's list. */
    return (int)(pCD1->pWsList[pCD1->currentWsc].wsID -
		 pCD2->pWsList[pCD2->currentWsc].wsID);

#else

    /* If no WSM, must be in same workspace if screen is same! */
    return 0;

#endif
}

/*
 *  Assumes: wmGD.clientResourceDB is non-NULL
 */
static char *
getClientResource(char *clientID, char *fmtStr)
{
    char resourceBuf[MAX_RESOURCE_LEN];
    char *resourceType;
    XrmValue resourceValue;

    sprintf(resourceBuf, fmtStr, clientID);
    if (XrmGetResource(wmGD.clientResourceDB, resourceBuf, resourceBuf,
		       &resourceType, &resourceValue))
	return (char *)resourceValue.addr;

    return (char *)NULL;
}

/*
 *  Assumes: pCD has non-NULL smClientID;
 *           wmGD.clientResourceDB is non-NULL
 */
static char *
getXSMPResource(ClientData *pCD, int resourceFlag, char *fmtStr)
{
    if (RESTORE_RESOURCE(pCD, resourceFlag))
	return getClientResource(pCD->smClientID, fmtStr);

    return (char *)NULL;
}

/*
 *  Return True if client is XSMP, False otherwise.
 */
static Boolean
findXSMPClientDBMatch(ClientData *pCD, char **workSpaceNamesP)
{
    if (pCD->smClientID != (String)NULL)
    {
	if (wmGD.clientResourceDB != (XrmDatabase)NULL)
	{
	    char *resourcePtr;

	    if ((resourcePtr = getXSMPResource(pCD, WMSAVE_X, xPositionStr))
		!= (char *)NULL)
	    {
		pCD->clientX = atoi(resourcePtr);
		pCD->clientFlags |= SM_X;
	    }

	    if ((resourcePtr = getXSMPResource(pCD, WMSAVE_Y, yPositionStr))
		!= (char *)NULL)
	    {
		pCD->clientY = atoi(resourcePtr);
		pCD->clientFlags |= SM_Y;
	    }

#ifndef WSM
	    if ((resourcePtr =
		 getXSMPResource(pCD, WMSAVE_ICON_X, iconXPosStr))
		!= (char *)NULL)
	    {
		ICON_X(pCD) = atoi(resourcePtr);
		pCD->clientFlags |= SM_ICON_X;
	    }

	    if ((resourcePtr =
		 getXSMPResource(pCD, WMSAVE_ICON_Y, iconYPosStr))
		!= (char *)NULL)
	    {
		ICON_Y(pCD) = atoi(resourcePtr);
		pCD->clientFlags |= SM_ICON_Y;
	    }
#endif

	    if ((resourcePtr = getXSMPResource(pCD, WMSAVE_WIDTH,
					       widthSizeStr))
		!= (char *)NULL)
	    {
		pCD->clientWidth = atoi(resourcePtr);
		pCD->clientFlags |= SM_WIDTH;
	    }

	    if ((resourcePtr = getXSMPResource(pCD, WMSAVE_HEIGHT,
					       heightSizeStr))
		!= (char *)NULL)
	    {
		pCD->clientHeight = atoi(resourcePtr);
		pCD->clientFlags |= SM_HEIGHT;
	    }

	    if ((resourcePtr = getXSMPResource(pCD, WMSAVE_STATE,
					       initialStateStr))
		!= (char *)NULL)
	    {
		pCD->clientState =
		    (strcmp(resourcePtr, normalStateStr) == 0) ?
			NORMAL_STATE : MINIMIZED_STATE;
		pCD->clientFlags |= SM_CLIENT_STATE;
	    }

#ifdef WSM
	    if ((workSpaceNamesP != (char **)NULL) &&
		((resourcePtr = getXSMPResource(pCD, WMSAVE_WORKSPACES,
						workspacesStr))
		 != (char *)NULL))
	    {
		*workSpaceNamesP = XtNewString(resourcePtr);
	    }
#endif
	}

	/* Always return True for XSMP clients. */
	return True;
    }

    return False;
}

static Boolean
getProxyClientInfo(ClientData *pCD, ProxyClientInfo *proxyClientInfo)
{
    XTextProperty textProperty;
    unsigned long i;

    /* WM_COMMAND is required; WM_CLIENT_MACHINE is optional. */
    if (!XGetTextProperty(wmGD.display, pCD->client, &textProperty,
			  XA_WM_COMMAND))
	return False;

    if ((textProperty.encoding != XA_STRING) ||
	(textProperty.format != 8) ||
	(textProperty.value[0] == '\0'))
    {
	if (textProperty.value)
	    free((char *)textProperty.value);

	return False;
    }

    /* Convert embedded NULL characters to space characters. */
    /* (If last char is NULL, leave it alone) */
    for (i = 0; i < textProperty.nitems - 1; i++)
    {
	if (textProperty.value[i] == '\0')
	    textProperty.value[i] = ' ';
    }

    proxyClientInfo->screen = pCD->pSD->screen;
    proxyClientInfo->wmCommand = (char *)textProperty.value;

    /* Since WM_CLIENT_MACHINE is optional, don't fail if not found. */
    if (XGetWMClientMachine(wmGD.display, pCD->client, &textProperty))
	proxyClientInfo->wmClientMachine = (char *)textProperty.value;
    else proxyClientInfo->wmClientMachine = (char *)NULL;

    proxyClientInfo->clientID = (char *)NULL;

    return True;
}

/*
 *  IMPORTANT: This function is called by XrmEnumerateDatabase().
 *  It calls other Xrm*() functions - if dtwm is threaded, THIS
 *  WILL HANG.  For now, dtwm is NOT threaded, so no problem.
 */
static Bool
cmpProxyClientProc(XrmDatabase *clientDB, XrmBindingList bindingList,
		   XrmQuarkList quarkList, XrmRepresentation *reps,
		   XrmValue *value, XPointer uData)
{
    char *clientScreen;
    char *wmCommand;
    char *wmClientMachine;
    char *clientID = (char *)value->addr;
    ProxyClientInfo *proxyClientInfo = (ProxyClientInfo *)uData;

    if (((wmCommand =
	  getClientResource(clientID, wmCommandStr)) == (char *)NULL) ||
	(strcmp(wmCommand, proxyClientInfo->wmCommand) != 0) ||
	((clientScreen =
	  getClientResource(clientID, screenStr)) == (char *)NULL) ||
	(atoi(clientScreen) != proxyClientInfo->screen))
	return FALSE;

    /* So far so good.  If WM_CLIENT_MACHINE missing from either, */
    /* or if it is set in both and it's the same, we've got a match! */
    if (!proxyClientInfo->wmClientMachine ||
	((wmClientMachine =
	  getClientResource(clientID, wmClientMachineStr)) == (char *)NULL) ||
	(strcmp(proxyClientInfo->wmClientMachine, wmClientMachine) == 0))
    {
	proxyClientInfo->clientID = clientID;
	return TRUE;
    }

    return FALSE;
}

static char *
findProxyClientID(ClientData *pCD)
{
    ProxyClientInfo proxyClientInfo;
    char *clientID = (char *)NULL;
    static XrmName proxyName[2] = {NULLQUARK, NULLQUARK};
    static XrmClass proxyClass[2] = {NULLQUARK, NULLQUARK};

    if (proxyName[0] == NULLQUARK)
    {
	proxyName[0] = XrmStringToName(proxyClientStr);
	proxyClass[0] = XrmStringToClass(proxyClientStr);
    }

    /*
     *  We need to match the screen and
     *  the WM_COMMAND and WM_CLIENT_MACHINE properties.
     */
    if (!getProxyClientInfo(pCD, &proxyClientInfo))
	return clientID;

    if (XrmEnumerateDatabase(wmGD.clientResourceDB, proxyName, proxyClass,
			     XrmEnumOneLevel, cmpProxyClientProc,
			     (XPointer)&proxyClientInfo))
	clientID = proxyClientInfo.clientID;

    if (proxyClientInfo.wmCommand)
	free(proxyClientInfo.wmCommand);
    if (proxyClientInfo.wmClientMachine)
	free(proxyClientInfo.wmClientMachine);

    return clientID;
}

/*
 *  Return True if client is *not* XSMP and is listed in the resource DB
 *  and no checkpoint done yet.  Also remove entry from DB if found.
 */
static Boolean
findProxyClientDBMatch(ClientData *pCD, char **workSpaceNamesP)
{
    if ((pCD->smClientID == (String)NULL) &&
	(wmGD.clientResourceDB != (XrmDatabase)NULL) &&
	(!smClientDBCheckpointed))
    {
	char *proxyClientID;

	if ((proxyClientID = findProxyClientID(pCD)) != (char *)NULL)
	{
	    char *resourcePtr;

	    if ((resourcePtr =
		 getClientResource(proxyClientID, xPositionStr))
		!= (char *)NULL)
	    {
		pCD->clientX = atoi(resourcePtr);
		pCD->clientFlags |= SM_X;
	    }

	    if ((resourcePtr =
		 getClientResource(proxyClientID, yPositionStr))
		!= (char *)NULL)
	    {
		pCD->clientY = atoi(resourcePtr);
		pCD->clientFlags |= SM_Y;
	    }

#ifndef WSM
	    if ((resourcePtr =
		 getClientResource(proxyClientID, iconXPosStr))
		!= (char *)NULL)
	    {
		ICON_X(pCD) = atoi(resourcePtr);
		pCD->clientFlags |= SM_ICON_X;
	    }

	    if ((resourcePtr =
		 getClientResource(proxyClientID, iconYPosStr))
		!= (char *)NULL)
	    {
		ICON_Y(pCD) = atoi(resourcePtr);
		pCD->clientFlags |= SM_ICON_Y;
	    }
#endif

	    if ((resourcePtr =
		 getClientResource(proxyClientID, widthSizeStr))
		!= (char *)NULL)
	    {
		pCD->clientWidth = atoi(resourcePtr);
		pCD->clientFlags |= SM_WIDTH;
	    }

	    if ((resourcePtr =
		 getClientResource(proxyClientID, heightSizeStr))
		!= (char *)NULL)
	    {
		pCD->clientHeight = atoi(resourcePtr);
		pCD->clientFlags |= SM_HEIGHT;
	    }

	    if ((resourcePtr =
		 getClientResource(proxyClientID, initialStateStr))
		!= (char *)NULL)
	    {
		pCD->clientState =
		    (strcmp(resourcePtr, normalStateStr) == 0) ?
			NORMAL_STATE : MINIMIZED_STATE;
		pCD->clientFlags |= SM_CLIENT_STATE;
	    }

#ifdef WSM
	    if ((workSpaceNamesP != (char **)NULL) &&
		((resourcePtr =
		  getClientResource(proxyClientID, workspacesStr))
		 != (char *)NULL))
	    {
		*workSpaceNamesP = XtNewString(resourcePtr);
	    }
#endif

#ifndef WSM
	    /* This is done in LoadClientIconPositions() if WSM defined. */
	    dbRemoveProxyClientEntry(proxyClientID);
#endif

	    return True;
	}
    }

    return False;
}

/*
 *  Translate the client geometry into what's needed on restore.
 */
static void
getClientGeometry(ClientData *pCD, int *clientX, int *clientY,
		  unsigned int *clientWd, unsigned int *clientHt)
{
    *clientX = pCD->clientX;
    *clientY = pCD->clientY;
    *clientWd = (pCD->widthInc != 0) ?
	(pCD->clientWidth - pCD->baseWidth) / pCD->widthInc :
	    pCD->clientWidth;
    *clientHt = (pCD->heightInc != 0) ?
	(pCD->clientHeight - pCD->baseHeight) / pCD->heightInc :
	    pCD->clientHeight;
}

/*
 *  Assumes: pCD->smClientID is not NULL
 */
static Boolean
saveXSMPClient(FILE *fp, ClientData *pCD)
{
    int clientX, clientY;
    unsigned int clientWd, clientHt;
    char *clientID = pCD->smClientID;

    fprintf(fp, dbClientFormat, XSMPClientStr, clientID, clientID);

    getClientGeometry(pCD, &clientX, &clientY, &clientWd, &clientHt);

    if (SAVE_RESOURCE(pCD, WMSAVE_X))
    {
	fprintf(fp, xPositionStr, clientID);
	fprintf(fp, intArg, clientX);
    }

    if (SAVE_RESOURCE(pCD, WMSAVE_Y))
    {
	fprintf(fp, yPositionStr, clientID);
	fprintf(fp, intArg, clientY);
    }

    if (!pCD->pSD->useIconBox)
    {
#ifdef WSM
	WmScreenData *pSD = pCD->pSD;
	WmWorkspaceData *pWS;
	int i;

	for (i = 0; i < pCD->numInhabited; i++)
	{
	    if ((pWS = GetWorkspaceData(pSD, pCD->pWsList[i].wsID))
		!= (WmWorkspaceData *)NULL)
	    {
		if (SAVE_RESOURCE(pCD, WMSAVE_ICON_X))
		{
		    fprintf(fp, iconXPosStr, clientID, pWS->name);
		    fprintf(fp, intArg, pCD->pWsList[i].iconX);
		}

		if (SAVE_RESOURCE(pCD, WMSAVE_ICON_Y))
		{
		    fprintf(fp, iconYPosStr, clientID, pWS->name);
		    fprintf(fp, intArg, pCD->pWsList[i].iconY);
		}
	    }
	}
#else
	if (SAVE_RESOURCE(pCD, WMSAVE_ICON_X))
	{
	    fprintf(fp, iconXPosStr, clientID);
	    fprintf(fp, intArg, ICON_X(pCD));
	}

	if (SAVE_RESOURCE(pCD, WMSAVE_ICON_Y))
	{
	    fprintf(fp, iconYPosStr, clientID);
	    fprintf(fp, intArg, ICON_Y(pCD));
	}
#endif
    }

    if (SAVE_RESOURCE(pCD, WMSAVE_WIDTH))
    {
	fprintf(fp, widthSizeStr, clientID);
	fprintf(fp, intArg, clientWd);
    }

    if (SAVE_RESOURCE(pCD, WMSAVE_HEIGHT))
    {
	fprintf(fp, heightSizeStr, clientID);
	fprintf(fp, intArg, clientHt);
    }

    if (SAVE_RESOURCE(pCD, WMSAVE_STATE))
    {
	int clientState;

#ifdef WSM
	clientState = pCD->clientState & ~UNSEEN_STATE;
#else
	clientState = pCD->clientState;
#endif

	fprintf(fp, initialStateStr, clientID);
	fprintf(fp, strArg, (clientState == NORMAL_STATE) ?
		normalStateStr : iconicStateStr);
    }

#ifdef WSM
    if (SAVE_RESOURCE(pCD, WMSAVE_WORKSPACES))
    {
	char *clientWorkspaces = getClientWorkspaces(pCD);

	if (clientWorkspaces != (char *)NULL)
	{
	    fprintf(fp, workspacesStr, clientID);
	    fprintf(fp, strArg, clientWorkspaces);
	    XtFree(clientWorkspaces);
	}
    }
#endif

    return True;
}

/*
 *  Assumes: pCD->smClientID is NULL
 */
static Boolean
saveProxyClient(FILE *fp, ClientData *pCD, int clientIDNum)
{
    char clientID[50];
    int clientState;
    ProxyClientInfo proxyClientInfo;
    int clientX, clientY;
    unsigned int clientWd, clientHt;
#ifdef WSM
    char *clientWorkspaces;
#endif

    if (!getProxyClientInfo(pCD, &proxyClientInfo))
	return False;

    sprintf(clientID, "%d", clientIDNum);
    fprintf(fp, dbClientFormat, proxyClientStr, clientID, clientID);

    fprintf(fp, screenStr, clientID);
    fprintf(fp, intArg, proxyClientInfo.screen);

    fprintf(fp, wmCommandStr, clientID);
    fprintf(fp, strArg, proxyClientInfo.wmCommand);
    free(proxyClientInfo.wmCommand);

    if (proxyClientInfo.wmClientMachine != (char *)NULL)
    {
	fprintf(fp, wmClientMachineStr, clientID);
	fprintf(fp, strArg, proxyClientInfo.wmClientMachine);
	free(proxyClientInfo.wmClientMachine);
    }

    getClientGeometry(pCD, &clientX, &clientY, &clientWd, &clientHt);

    fprintf(fp, xPositionStr, clientID);
    fprintf(fp, intArg, clientX);

    fprintf(fp, yPositionStr, clientID);
    fprintf(fp, intArg, clientY);

    if (!pCD->pSD->useIconBox)
    {
#ifdef WSM
	WmScreenData *pSD = pCD->pSD;
	WmWorkspaceData *pWS;
	int i;

	for (i = 0; i < pCD->numInhabited; i++)
	{
	    if ((pWS = GetWorkspaceData(pSD, pCD->pWsList[i].wsID))
		!= (WmWorkspaceData *)NULL)
	    {
		fprintf(fp, iconXPosStr, clientID, pWS->name);
		fprintf(fp, intArg, pCD->pWsList[i].iconX);

		fprintf(fp, iconYPosStr, clientID, pWS->name);
		fprintf(fp, intArg, pCD->pWsList[i].iconY);
	    }
	}
#else
	fprintf(fp, iconXPosStr, clientID);
	fprintf(fp, intArg, ICON_X(pCD));

	fprintf(fp, iconYPosStr, clientID);
	fprintf(fp, intArg, ICON_Y(pCD));
#endif
    }

    fprintf(fp, widthSizeStr, clientID);
    fprintf(fp, intArg, clientWd);

    fprintf(fp, heightSizeStr, clientID);
    fprintf(fp, intArg, clientHt);

#ifdef WSM
    clientState = pCD->clientState & ~UNSEEN_STATE;
#else
    clientState = pCD->clientState;
#endif

    fprintf(fp, initialStateStr, clientID);
    fprintf(fp, strArg, (clientState == NORMAL_STATE) ?
	    normalStateStr : iconicStateStr);

#ifdef WSM
    clientWorkspaces = getClientWorkspaces(pCD);
    if (clientWorkspaces != (char *)NULL)
    {
	fprintf(fp, workspacesStr, clientID);
	fprintf(fp, strArg, clientWorkspaces);
	XtFree(clientWorkspaces);
    }
#endif

    return True;
}

static void
dbRemoveProxyClientEntry(char *proxyClientID)
{
    char resourceBuf[MAX_RESOURCE_LEN];

    /* Remove entry from DB.  Since Xrm does not provide a means */
    /* of removing something from the DB, we blank out key info. */
    sprintf(resourceBuf, wmCommandStr, proxyClientID);
    strcat(resourceBuf, ":");
    XrmPutLineResource(&wmGD.clientResourceDB, resourceBuf);
}

/*
 *  Add callbacks used in session management.
 */
void
AddSMCallbacks(void)
{
    XtAddCallback(wmGD.topLevelW, XtNsaveCallback,
		  smSaveYourselfCallback, (XtPointer)NULL);
    XtAddCallback(wmGD.topLevelW, XtNdieCallback,
		  smDieCallback, (XtPointer)NULL);
}

/*
 *  Resign from session management, closing any connections made.
 */
void
ResignFromSM(void)
{
    if (wmGD.topLevelW)
    {
	XtVaSetValues(wmGD.topLevelW,
		      XtNjoinSession, False,
		      NULL);
    }
}

/*
 *  Exit the WM, being polite by first resigning from session mgmt.
 */
void
ExitWM(int exitCode)
{
    ResignFromSM();
    exit(exitCode);
}

/*
 *  Read our private database of client resources.
 */
XrmDatabase
LoadClientResourceDB(void)
{
    char dbFileName[MAXPATHLEN];

#ifndef WSM
    getClientDBName();
#endif
    buildDBFileName(dbFileName, False);

    return XrmGetFileDatabase(dbFileName);
}

/*
 *  Write our private database of client resources.
 */
XrmDatabase
SaveClientResourceDB(void)
{
    String mySessionID;
    char dbFileName[MAXPATHLEN];
    FILE *fp;
    int scr;
    WmScreenData *pSD;
    ClientData *pCD;
    int clientIDNum = 0;
    ClientListEntry *pCL;

    /* Iterate through client list, saving */
    /* appropriate resources for each. */
#ifndef WSM
    setClientDBName();
#endif
    buildDBFileName(dbFileName, True);
    if ((fp = fopen(dbFileName, "w")) == (FILE *)NULL)
	return (XrmDatabase)NULL;

    XtVaGetValues(wmGD.topLevelW,
		  XtNsessionID, &mySessionID,
		  NULL);
    fprintf(fp, dbHeader, dtwmFileName, "dtwm Version XSMP1.0",
	    (mySessionID != (String)NULL) ? mySessionID : "");

    for (scr = 0; scr < wmGD.numScreens; scr++)
    {
	pSD = &(wmGD.Screens[scr]);

	for (pCL = pSD->clientList;
	     pCL != (ClientListEntry *)NULL;
	     pCL = pCL->nextSibling)
	{
	    /* Each client may be in list twice: normal & icon */
	    if (pCL->type != NORMAL_STATE)
		continue;

	    pCD = pCL->pCD;

	    if (pCD->smClientID != (String)NULL)
	    {
		saveXSMPClient(fp, pCD);
	    }
	    else
	    {
		if (saveProxyClient(fp, pCD, clientIDNum))
		    clientIDNum++;
	    }
	}
    }

    fclose(fp);

    /* Retrieve database from file. */
    return XrmGetFileDatabase(dbFileName);
}

/*
 *  As with FindDtSessionMatch(), sets properties and then returns
 *  an allocated string of workspace names.  This string must be
 *  freed by the caller using XtFree().
 */
Boolean
FindClientDBMatch(ClientData *pCD, char **workSpaceNamesP)
{
    return (findXSMPClientDBMatch(pCD, workSpaceNamesP) ||
	    findProxyClientDBMatch(pCD, workSpaceNamesP));
}

Boolean
GetSmClientIdClientList(ClientData ***clients, int *nClients)
{
    int scr;
    WmScreenData *pSD;
    ClientData *pCD;
    ClientListEntry *pCL;

    *nClients = 0;
    *clients = (ClientData **)NULL;
    for (scr = 0; scr < wmGD.numScreens; scr++)
    {
	pSD = &(wmGD.Screens[scr]);

	for (pCL = pSD->clientList;
	     pCL != (ClientListEntry *)NULL;
	     pCL = pCL->nextSibling)
	{
	    /* Each client may be in list twice: normal & icon */
	    if (pCL->type != NORMAL_STATE)
		continue;

	    pCD = pCL->pCD;

	    if (pCD->smClientID != (String)NULL)
	    {
		/* addClientToList() reclaims memory on failure. */
		if (!addClientToList(clients, nClients, pCD))
		    return False;
	    }
	}
    }

    return True;
}

void
SortClientListByWorkspace(ClientData **clients, int nClients)
{
    if (nClients > 0)
    {
	qsort((void *)clients, nClients,
	      sizeof(ClientData *), clientWorkspaceCompare);
    }
}

#ifdef WSM
/* This needs to be called if WSM defined; if WSM not defined, icon */
/* positions are read at the same time as other resources. */
void
LoadClientIconPositions(ClientData *pCD)
{
    char resourceBuf[MAX_RESOURCE_LEN];
    WmScreenData *pSD = pCD->pSD;
    WmWorkspaceData *pWS;
    int i;
    char *resourcePtr;

    if (wmGD.clientResourceDB == (XrmDatabase)NULL)
	return;

    if (pCD->smClientID != (String)NULL)
    {
	for (i = 0; i < pCD->numInhabited; i++)
	{
	    if ((pWS = GetWorkspaceData(pSD, pCD->pWsList[i].wsID))
		!= (WmWorkspaceData *)NULL)
	    {
		sprintf(resourceBuf, iconXPosStr, "%s", pWS->name);
		if ((resourcePtr =
		     getXSMPResource(pCD, WMSAVE_ICON_X, resourceBuf))
		    != (char *)NULL)
		{
		    pCD->pWsList[i].iconX = atoi(resourcePtr);
		    pCD->clientFlags |= SM_ICON_X;
		}

		sprintf(resourceBuf, iconYPosStr, "%s", pWS->name);
		if ((resourcePtr =
		     getXSMPResource(pCD, WMSAVE_ICON_Y, resourceBuf))
		    != (char *)NULL)
		{
		    pCD->pWsList[i].iconY = atoi(resourcePtr);
		    pCD->clientFlags |= SM_ICON_Y;
		}
	    }
	}
	return;
    }

    /* Proxy client */
    if (!smClientDBCheckpointed)
    {
	char *proxyClientID;

	if ((proxyClientID = findProxyClientID(pCD)) != (char *)NULL)
	{
	    for (i = 0; i < pCD->numInhabited; i++)
	    {
		if ((pWS = GetWorkspaceData(pSD, pCD->pWsList[i].wsID))
		    != (WmWorkspaceData *)NULL)
		{
		    sprintf(resourceBuf, iconXPosStr, "%s", pWS->name);
		    if ((resourcePtr =
			 getClientResource(proxyClientID, resourceBuf))
			!= (char *)NULL)
		    {
			pCD->pWsList[i].iconX = atoi(resourcePtr);
			pCD->clientFlags |= SM_ICON_X;
		    }

		    sprintf(resourceBuf, iconYPosStr, "%s", pWS->name);
		    if ((resourcePtr =
			 getClientResource(proxyClientID, resourceBuf))
			!= (char *)NULL)
		    {
			pCD->pWsList[i].iconY = atoi(resourcePtr);
			pCD->clientFlags |= SM_ICON_Y;
		    }
		}
	    }
	    dbRemoveProxyClientEntry(proxyClientID);
	}
    }
}
#endif /* WSM */
