/*
 *       Copyright 1988 by Evans & Sutherland Computer Corporation,
 *                          Salt Lake City, Utah
 *  Portions Copyright 1989 by the Massachusetts Institute of Technology
 *                        Cambridge, Massachusetts
 *
 * Copyright 1992 Claude Lecommandeur.
 */

/*********************************************************************
 *
 * This module has been modified by
 * Matthew McNeill (21 Mar 1997) - University of Durham (UK)
 * for the support of the X Session Management Protocol
 *
 *********************************************************************
 *
 * Copyright (c) 1996-1997 The University of Durham, UK -
 * Department of Computer Science.
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * licence or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation, provided that usage, copying, modification
 * or distribution is not for direct commercial advantage and that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software. To use, copy, modify or distribute this
 * software and its documentation otherwise requires a fee and/or specific
 * permission.
 *
 * IN NO EVENT SHALL THE UNIVERSITY OF DURHAM BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * DURHAM HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF DURHAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF DURHAM HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 *
 ********************************************************************/

/**********************************************************************
 *
 * $XConsortium: session.c,v 1.18 95/01/04 22:28:37 mor Exp $
 *
 * Session support for the X11R6 XSMP (X Session Management Protocol)
 *
 * 95/01/04 Ralph Mor, X Consortium        Initial Version.
 *
 * Do the necessary modification to be integrated in ctwm.
 * Can no longer be used for the standard twm.
 *
 * 21 Mar 1997  Matthew McNeill, Durham University  Modified Version.
 *
 **********************************************************************/

#include "ctwm.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>  // For umask
#include <limits.h>    // PATH_MAX

#include <X11/Xatom.h>

#include "ctwm_atoms.h"
#include "ctwm_shutdown.h"
#include "icons.h"
#include "list.h"
#include "screen.h"
#include "session.h"


SmcConn smcConn = NULL;
static XtInputId iceInputId;
static char *twm_clientId;
static TWMWinConfigEntry *winConfigHead = NULL;
static bool sent_save_done = false;

static void SaveYourselfCB(SmcConn smcCon, SmPointer clientData,
                           int saveType, Bool shutdown, int interactStyle,
                           Bool fast);
static char *GetClientID(Window window);
static char *GetWindowRole(Window window);
static int WriteWinConfigEntry(FILE *configFile, TwmWindow *theWindow,
                        char *clientId, char *windowRole);
static int ReadWinConfigEntry(FILE *configFile, unsigned short version,
                       TWMWinConfigEntry **pentry);
static void SaveYourselfPhase2CB(SmcConn smcCon, SmPointer clientData);
static void DieCB(SmcConn smcCon, SmPointer clientData);
static void SaveCompleteCB(SmcConn smcCon, SmPointer clientData);
static void ShutdownCancelledCB(SmcConn smcCon, SmPointer clientData);
static void ProcessIceMsgProc(XtPointer client_data, int *source, XtInputId *id);

#define SAVEFILE_VERSION 2


/*===[ Get Client SM_CLIENT_ID ]=============================================*/

static char *GetClientID(Window window)
/* This function returns the value of the session manager client ID property
 * given a valid window handle. If no such property exists on a window then
 * null is returned
 */
{
	char *client_id = NULL;
	Window client_leader;
	XTextProperty tp;
	Atom actual_type;
	int actual_format;
	unsigned long nitems;
	unsigned long bytes_after;
	Window *prop = NULL;

	if(XGetWindowProperty(dpy, window, XA_WM_CLIENT_LEADER,
	                      0L, 1L, False, AnyPropertyType, &actual_type, &actual_format,
	                      &nitems, &bytes_after, (unsigned char **)&prop) == Success) {
		if(actual_type == XA_WINDOW && actual_format == 32 &&
		                nitems == 1 && bytes_after == 0) {
			client_leader = *prop;

			if(XGetTextProperty(dpy, client_leader, &tp, XA_SM_CLIENT_ID)) {
				if(tp.encoding == XA_STRING &&
				                tp.format == 8 && tp.nitems != 0) {
					client_id = (char *) tp.value;
				}
			}
		}

		if(prop) {
			XFree(prop);
		}
	}

	return client_id;
}

/*===[ Get Window Role ]=====================================================*/

static char *GetWindowRole(Window window)
/* this function returns the WM_WINDOW_ROLE property of a window
 */
{
	XTextProperty tp;

	if(XGetTextProperty(dpy, window, &tp, XA_WM_WINDOW_ROLE)) {
		if(tp.encoding == XA_STRING && tp.format == 8 && tp.nitems != 0) {
			return ((char *) tp.value);
		}
	}

	return NULL;
}

/*===[ Various file write procedures ]=======================================*/

static int write_byte(FILE *file, unsigned char b)
{
	if(fwrite((char *) &b, 1, 1, file) != 1) {
		return 0;
	}
	return 1;
}

/*---------------------------------------------------------------------------*/

static int write_ushort(FILE *file, unsigned short s)
{
	unsigned char   file_short[2];

	file_short[0] = (s & (unsigned)0xff00) >> 8;
	file_short[1] = s & 0xff;
	if(fwrite((char *) file_short, (int) sizeof(file_short), 1, file) != 1) {
		return 0;
	}
	return 1;
}

/*---------------------------------------------------------------------------*/

static int write_short(FILE *file, short s)
{
	unsigned char   file_short[2];

	file_short[0] = (s & (unsigned)0xff00) >> 8;
	file_short[1] = s & 0xff;
	if(fwrite((char *) file_short, (int) sizeof(file_short), 1, file) != 1) {
		return 0;
	}
	return 1;
}

/*---------------------------------------------------------------------------*
 * Matthew McNeill Feb 1997 - required to save the occupation state as an
 *                            integer.
 */

static int write_int(FILE *file, int i)
{
	unsigned char   file_int[4];

	file_int[0] = (i & (unsigned)0xff000000) >> 24;
	file_int[1] = (i & (unsigned)0x00ff0000) >> 16;
	file_int[2] = (i & (unsigned)0x0000ff00) >> 8;
	file_int[3] = (i & (unsigned)0x000000ff);
	if(fwrite((char *) file_int, (int) sizeof(file_int), 1, file) != 1) {
		return 0;
	}
	return 1;
}

/*---------------------------------------------------------------------------*/

static int write_counted_string(FILE *file, char *string)
{
	if(string) {
		unsigned char count = strlen(string);

		if(write_byte(file, count) == 0) {
			return 0;
		}
		if(fwrite(string, (int) sizeof(char), (int) count, file) != count) {
			return 0;
		}
	}
	else {
		if(write_byte(file, 0) == 0) {
			return 0;
		}
	}

	return 1;
}

/*===[ various file read procedures ]========================================*/

static int read_byte(FILE *file, unsigned char *bp)
{
	if(fread((char *) bp, 1, 1, file) != 1) {
		return 0;
	}
	return 1;
}

/*---------------------------------------------------------------------------*/

static int read_ushort(FILE *file, unsigned short *shortp)
{
	unsigned char   file_short[2];

	if(fread((char *) file_short, (int) sizeof(file_short), 1, file) != 1) {
		return 0;
	}
	*shortp = file_short[0] * 256 + file_short[1];
	return 1;
}

/*---------------------------------------------------------------------------*/

static int read_short(FILE *file, short *shortp)
{
	unsigned char   file_short[2];

	if(fread((char *) file_short, (int) sizeof(file_short), 1, file) != 1) {
		return 0;
	}
	*shortp = file_short[0] * 256 + file_short[1];
	return 1;
}

/*---------------------------------------------------------------------------*
 * Matthew McNeill Feb 1997 - required to save the occupation state as an
 *                            integer.
 */

static int read_int(FILE *file, int *intp)
{
	unsigned char   file_int[4];

	if(fread((char *) file_int, (int) sizeof(file_int), 1, file) != 1) {
		return 0;
	}
	*intp = (((int) file_int[0]) << 24) & 0xff000000;
	*intp += (((int) file_int[1]) << 16) & 0x00ff0000;
	*intp += (((int) file_int[2]) << 8)  & 0x0000ff00;
	*intp += ((int) file_int[3]);
	return 1;
}

/*---------------------------------------------------------------------------*/

static int read_counted_string(FILE *file, char **stringp)
{
	unsigned char  len;
	char           *data;

	if(read_byte(file, &len) == 0) {
		return 0;
	}
	if(len == 0) {
		data = 0;
	}
	else {
		data = malloc((unsigned) len + 1);
		if(!data) {
			return 0;
		}
		if(fread(data, (int) sizeof(char), (int) len, file) != len) {
			free(data);
			return 0;
		}
		data[len] = '\0';
	}
	*stringp = data;
	return 1;
}

/*===[ Definition of a window config entry ]===================================
 *
 * An entry in the saved window config file looks like this:
 *
 * FIELD                                BYTES
 * -----                                ----
 * SM_CLIENT_ID ID len                  1              (may be 0)
 * SM_CLIENT_ID                         LIST of bytes  (may be NULL)
 *
 * WM_WINDOW_ROLE length                1              (may be 0)
 * WM_WINDOW_ROLE                       LIST of bytes  (may be NULL)
 *
 * if no WM_WINDOW_ROLE (length = 0)
 *
 *   WM_CLASS "res name" length         1
 *   WM_CLASS "res name"                LIST of bytes
 *   WM_CLASS "res class" length        1
 *   WM_CLASS "res class"               LIST of bytes
 *   WM_NAME length                     1               (0 if name changed)
 *   WM_NAME                            LIST of bytes
 *   WM_COMMAND arg count               1               (0 if no SM_CLIENT_ID)
 *   For each arg in WM_COMMAND
 *      arg length                      1
 *      arg                             LIST of bytes
 *
 * Iconified bool                       1
 * Icon info present bool               1
 *
 * if icon info present
 *      icon x                          2
 *      icon y                          2
 *
 * Geom x                               2
 * Geom y                               2
 * Geom width                           2
 * Geom height                          2
 *
 * Width ever changed by user           1
 * Height ever changed by user          1
 *
 * ------------------[ Matthew McNeill Feb 1997 ]----------------------------
 *
 * Workspace Occupation                 4
 *
 */


/*===[ Write Window Config Entry to file ]===================================*/

static int WriteWinConfigEntry(FILE *configFile, TwmWindow *theWindow,
                        char *clientId, char *windowRole)
/* this function writes a window configuration entry of a given window to
 * the given configuration file
 */
{
	char **wm_command;
	int wm_command_count, i;

	/* ...unless the config file says otherwise. */
	if(LookInList(Scr == NULL ? ScreenList [0]->DontSave : Scr->DontSave,
	                theWindow->name, &theWindow->class)) {
		return 1;
	}

	if(!write_counted_string(configFile, clientId)) {
		return 0;
	}

	if(!write_counted_string(configFile, windowRole)) {
		return 0;
	}

	if(!windowRole) {
		if(!write_counted_string(configFile, theWindow->class.res_name)) {
			return 0;
		}
		if(!write_counted_string(configFile, theWindow->class.res_class)) {
			return 0;
		}
		if(theWindow->nameChanged) {
			/*
			 * If WM_NAME changed on this window, we can't use it as
			 * a criteria for looking up window configurations.  See the
			 * longer explanation in the GetWindowConfig() function below.
			 */

			if(!write_counted_string(configFile, NULL)) {
				return 0;
			}
		}
		else {
			if(!write_counted_string(configFile, theWindow->name)) {
				return 0;
			}
		}

		wm_command = NULL;
		wm_command_count = 0;
		XGetCommand(dpy, theWindow->w, &wm_command, &wm_command_count);

		if(clientId || !wm_command || wm_command_count == 0) {
			if(!write_byte(configFile, 0)) {
				return 0;
			}
		}
		else {
			if(!write_byte(configFile, (char) wm_command_count)) {
				return 0;
			}
			for(i = 0; i < wm_command_count; i++)
				if(!write_counted_string(configFile, wm_command[i])) {
					return 0;
				}
			XFreeStringList(wm_command);
		}
	}

	/* ===================[ Matthew McNeill Feb 1997 ]========================= *
	 * there has been a structural change to TwmWindow in ctwm. The Icon information
	 * is in a sub-structure now. The presence of icon information is not indicative
	 * of its current state. There is a new boolean condition for this (isicon)
	 */

	if(!write_byte(configFile, theWindow->isicon ? 1 : 0)) {
		return 0;        /* iconified */
	}

	/* ===================[ Matthew McNeill Feb 1997 ]========================= *
	 * there has been a structural change to TwmWindow in ctwm. The Icon information
	 * is in a sub-structure now, if there is no icon, this sub-structure does
	 * not exist and the attempted access (below) causes a core dump.
	 * we need to check that the structure exists before trying to access it
	 */
	if(theWindow->icon) {
		if(!write_byte(configFile, theWindow->icon->w ? 1 : 0)) {
			return 0;        /* icon info exists */
		}
		if(theWindow->icon->w) {
			int icon_x, icon_y;
			XGetGeometry(dpy, theWindow->icon->w, &JunkRoot, &icon_x,
			             &icon_y, &JunkWidth, &JunkHeight, &JunkBW, &JunkDepth);
			if(!write_short(configFile, (short) icon_x)) {
				return 0;
			}
			if(!write_short(configFile, (short) icon_y)) {
				return 0;
			}
		}
	}
	else {
		if(!write_byte(configFile, 0)) {
			return 0;        /* icon does not have any information */
		}
	}
	/* ======================================================================= */

	if(!write_short(configFile, (short) theWindow->frame_x)) {
		return 0;
	}
	if(!write_short(configFile, (short) theWindow->frame_y)) {
		return 0;
	}
	if(!write_ushort(configFile, (unsigned short) theWindow->attr.width)) {
		return 0;
	}
	if(!write_ushort(configFile, (unsigned short) theWindow->attr.height)) {
		return 0;
	}
	if(!write_byte(configFile, theWindow->widthEverChangedByUser ? 1 : 0)) {
		return 0;
	}
	if(!write_byte(configFile, theWindow->heightEverChangedByUser ? 1 : 0)) {
		return 0;
	}

	/* ===================[ Matthew McNeill Feb 1997 ]=======================*
	 * write an extra piece of information to the file, this is the occupation
	 * number and is a bit field of the workspaces occupied by the client.
	 */

	if(!write_int(configFile, theWindow->occupation)) {
		return 0;
	}

	/* ======================================================================*/

	return 1;
}

/*===[ Read Window Configuration Entry ]=====================================*/

static int ReadWinConfigEntry(FILE *configFile, unsigned short version,
                       TWMWinConfigEntry **pentry)
/* this function reads the next window configuration entry from the given file
 * else it returns 0 if none exists or there is a problem
 */
{
	TWMWinConfigEntry *entry;
	unsigned char byte;
	int i;

	*pentry = entry = calloc(1, sizeof(TWMWinConfigEntry));
	if(!*pentry) {
		return 0;
	}

	if(!read_counted_string(configFile, &entry->client_id)) {
		goto give_up;
	}

	if(!read_counted_string(configFile, &entry->window_role)) {
		goto give_up;
	}

	if(!entry->window_role) {
		if(!read_counted_string(configFile, &entry->class.res_name)) {
			goto give_up;
		}
		if(!read_counted_string(configFile, &entry->class.res_class)) {
			goto give_up;
		}
		if(!read_counted_string(configFile, &entry->wm_name)) {
			goto give_up;
		}

		if(!read_byte(configFile, &byte)) {
			goto give_up;
		}
		entry->wm_command_count = byte;

		if(entry->wm_command_count == 0) {
			entry->wm_command = NULL;
		}
		else {
			entry->wm_command = calloc(entry->wm_command_count, sizeof(char *));

			if(!entry->wm_command) {
				goto give_up;
			}

			for(i = 0; i < entry->wm_command_count; i++)
				if(!read_counted_string(configFile, &entry->wm_command[i])) {
					goto give_up;
				}
		}
	}

	if(!read_byte(configFile, &byte)) {
		goto give_up;
	}

	entry->iconified = byte;

	if(!read_byte(configFile, &byte)) {
		goto give_up;
	}

	entry->icon_info_present = byte;

	if(entry->icon_info_present) {
		if(!read_short(configFile, (short *) &entry->icon_x)) {
			goto give_up;
		}
		if(!read_short(configFile, (short *) &entry->icon_y)) {
			goto give_up;
		}
	}

	if(!read_short(configFile, (short *) &entry->x)) {
		goto give_up;
	}
	if(!read_short(configFile, (short *) &entry->y)) {
		goto give_up;
	}
	if(!read_ushort(configFile, &entry->width)) {
		goto give_up;
	}
	if(!read_ushort(configFile, &entry->height)) {
		goto give_up;
	}

	if(version > 1) {
		if(!read_byte(configFile, &byte)) {
			goto give_up;
		}
		entry->width_ever_changed_by_user = byte;

		if(!read_byte(configFile, &byte)) {
			goto give_up;
		}
		entry->height_ever_changed_by_user = byte;
	}
	else {
		entry->width_ever_changed_by_user = False;
		entry->height_ever_changed_by_user = False;
	}

	/* ===================[ Matthew McNeill Feb 1997 ]======================= *
	 * read in the occupation information to restore the windows to the
	 * correct workspaces.
	 */

	if(!read_int(configFile, &entry->occupation)) {
		goto give_up;
	}

	/* ====================================================================== */

	return 1;

give_up:

	if(entry->client_id) {
		free(entry->client_id);
	}
	if(entry->window_role) {
		free(entry->window_role);
	}
	if(entry->class.res_name) {
		free(entry->class.res_name);
	}
	if(entry->class.res_class) {
		free(entry->class.res_class);
	}
	if(entry->wm_name) {
		free(entry->wm_name);
	}
	if(entry->wm_command_count && entry->wm_command) {
		for(i = 0; i < entry->wm_command_count; i++)
			if(entry->wm_command[i]) {
				free(entry->wm_command[i]);
			}
	}
	if(entry->wm_command) {
		free(entry->wm_command);
	}

	free(entry);
	*pentry = NULL;

	return 0;
}

/*===[ Read In Win Config File ]=============================================*/

void ReadWinConfigFile(char *filename)
/* this function reads the window configuration file and stores the information
 * in a data structure which is returned
 */
{
	FILE *configFile;
	TWMWinConfigEntry *entry;
	int done = 0;
	unsigned short version;

	configFile = fopen(filename, "rb");
	if(!configFile) {
		return;
	}

	if(!read_ushort(configFile, &version) ||
	                version > SAVEFILE_VERSION) {
		done = 1;
	}

	while(!done) {
		if(ReadWinConfigEntry(configFile, version, &entry)) {
			entry->next = winConfigHead;
			winConfigHead = entry;
		}
		else {
			done = 1;
		}
	}

	fclose(configFile);
}

/*===[ Get Window Configuration ]============================================*
 * Matthew McNeill Feb 1997 - added extra parameter (occupation) to return
 *                            restored occupation of the window
 */

int GetWindowConfig(TwmWindow *theWindow, short *x, short *y,
                    unsigned short *width, unsigned short *height,
                    bool *iconified, bool *icon_info_present,
                    short *icon_x, short *icon_y,
                    bool *width_ever_changed_by_user,
                    bool *height_ever_changed_by_user,
                    int *occupation) /* <== [ Matthew McNeill Feb 1997 ] == */
/* This function attempts to extract all the relevant information from the
 * given window and return values via the rest of the parameters to the
 * function
 */
{
	char *clientId, *windowRole;
	TWMWinConfigEntry *ptr;
	int found = 0;

	ptr = winConfigHead;

	if(!ptr) {
		return 0;
	}

	clientId = GetClientID(theWindow->w);
	windowRole = GetWindowRole(theWindow->w);

	while(ptr && !found) {
		int client_id_match = (!clientId && !ptr->client_id) ||
		                      (clientId && ptr->client_id &&
		                       strcmp(clientId, ptr->client_id) == 0);

		if(!ptr->tag && client_id_match) {
			if(windowRole || ptr->window_role) {
				found = (windowRole && ptr->window_role &&
				         strcmp(windowRole, ptr->window_role) == 0);
			}
			else {
				/*
				 * Compare WM_CLASS + only compare WM_NAME if the
				 * WM_NAME in the saved file is non-NULL.  If the
				 * WM_NAME in the saved file is NULL, this means that
				 * the client changed the value of WM_NAME during the
				 * session, and we can not use it as a criteria for
				 * our search.  For example, with xmh, at save time
				 * the window name might be "xmh: folderY".  However,
				 * if xmh does not properly restore state when it is
				 * restarted, the initial window name might be
				 * "xmh: folderX".  This would cause the window manager
				 * to fail in finding the saved window configuration.
				 * The best we can do is ignore WM_NAME if its value
				 * changed in the previous session.
				 */

				if(strcmp(theWindow->class.res_name,
				                ptr->class.res_name) == 0 &&
				                strcmp(theWindow->class.res_class,
				                       ptr->class.res_class) == 0 &&
				                (ptr->wm_name == NULL ||
				                 strcmp(theWindow->name, ptr->wm_name) == 0)) {
					if(clientId) {
						/*
						 * If a client ID was present, we should not check
						 * WM_COMMAND because Xt will put a -xtsessionID arg
						 * on the command line.
						 */

						found = 1;
					}
					else {
						/*
						 * For non-XSMP clients, also check WM_COMMAND.
						 */

						char **wm_command = NULL;
						int wm_command_count = 0, i;

						XGetCommand(dpy, theWindow->w,
						            &wm_command, &wm_command_count);

						if(wm_command_count == ptr->wm_command_count) {
							for(i = 0; i < wm_command_count; i++)
								if(strcmp(wm_command[i],
								                ptr->wm_command[i]) != 0) {
									break;
								}

							if(i == wm_command_count) {
								found = 1;
							}
						}
					}
				}
			}
		}

		if(!found) {
			ptr = ptr->next;
		}
	}

	if(found) {
		*x = ptr->x;
		*y = ptr->y;
		*width = ptr->width;
		*height = ptr->height;
		*iconified = ptr->iconified;
		*icon_info_present = ptr->icon_info_present;
		*width_ever_changed_by_user = ptr->width_ever_changed_by_user;
		*height_ever_changed_by_user = ptr->height_ever_changed_by_user;

		if(*icon_info_present) {
			*icon_x = ptr->icon_x;
			*icon_y = ptr->icon_y;
		}

		*occupation = ptr->occupation; /* <== [ Matthew McNeill Feb 1997 ] == */

		ptr->tag = 1;
	}
	else {
		*iconified = 0;
	}

	if(clientId) {
		XFree(clientId);
	}

	if(windowRole) {
		XFree(windowRole);
	}

	return found;
}

/*===[ Unique Filename Generator ]===========================================*/

static char *unique_filename(char *path, char *prefix, int *fd)
/* this function attempts to allocate a temporary filename to store the
 * information of the windows
 */
{
#ifdef MISSING_MKSTEMP
	char *name;

	while(1) {
		name = (char *) tempnam(path, prefix);
		*fd = open(name, O_CREAT | O_EXCL);
		if(*fd >= 0) {
			return name;
		}
		free(name);
	}
#else
	char *tempFile;
	mode_t prev_umask;

	asprintf(&tempFile, "%s/%sXXXXXX", path, prefix);
	prev_umask = umask(077);
	*fd = mkstemp(tempFile);
	umask(prev_umask);
	if(*fd >= 0) {
		return tempFile;
	}
	else {
		free(tempFile);
		return NULL;
	}
#endif
}

/*===[ SAVE WINDOW INFORMATION ]=============================================*/

#ifndef PATH_MAX
#  define PATH_MAX 1023
#endif

static void SaveYourselfPhase2CB(SmcConn smcCon, SmPointer clientData)
/* this is where all the work is done in saving the state of the windows.
 * it is not done in Phase One because phase one is used for the other clients
 * to make sure that all the property information on their windows is correct
 * and up to date
 */
{
	int scrnum;
	ScreenInfo *theScreen;
	TwmWindow *theWindow;
	char *clientId, *windowRole;
	FILE *configFile = NULL;
	char *path;
	char *filename = NULL;
	Bool success = False;
	SmProp prop1, prop2, prop3, *props[3];
	SmPropValue prop1val, prop2val, prop3val;
	char discardCommand[PATH_MAX + 4];
	int numVals, i;
	static int first_time = 1;
	int configFd;

	if(first_time) {
		char userId[20];
		char hint = SmRestartIfRunning;

		prop1.name = SmProgram;
		prop1.type = SmARRAY8;
		prop1.num_vals = 1;
		prop1.vals = &prop1val;
		prop1val.value = Argv[0];
		prop1val.length = strlen(Argv[0]);

		sprintf(userId, "%d", getuid());
		prop2.name = SmUserID;
		prop2.type = SmARRAY8;
		prop2.num_vals = 1;
		prop2.vals = &prop2val;
		prop2val.value = (SmPointer) userId;
		prop2val.length = strlen(userId);

		prop3.name = SmRestartStyleHint;
		prop3.type = SmCARD8;
		prop3.num_vals = 1;
		prop3.vals = &prop3val;
		prop3val.value = (SmPointer) &hint;
		prop3val.length = 1;

		props[0] = &prop1;
		props[1] = &prop2;
		props[2] = &prop3;

		SmcSetProperties(smcCon, 3, props);

		first_time = 0;
	}

	path = getenv("SM_SAVE_DIR");
	if(!path) {
		path = getenv("HOME");
		if(!path) {
			path = ".";
		}
	}
	/*==============[ Matthew McNeill Feb 1997 ]==============*
	 *        changed the unique name to CTWM rather than TWM
	 *        this is tidier and more functional and prevents
	 *        TWM picking up CTWM config files. The format is
	 *        no longer the same since the new format supports
	 *        virtaul workspaces.
	 *========================================================*/
	if((filename = unique_filename(path, ".ctwm", &configFd)) == NULL) {
		goto bad;
	}

	if(!(configFile = fdopen(configFd, "wb"))) { /* wb = write binary */
		goto bad;
	}

	if(!write_ushort(configFile, SAVEFILE_VERSION)) {
		goto bad;
	}

	success = True;

	for(scrnum = 0; scrnum < NumScreens && success; scrnum++) {
		if(ScreenList[scrnum] != NULL) {
			theScreen = ScreenList[scrnum];
			theWindow = theScreen->FirstWindow;

			while(theWindow && success) {
				clientId = GetClientID(theWindow->w);
				windowRole = GetWindowRole(theWindow->w);

				if(!WriteWinConfigEntry(configFile, theWindow,
				                        clientId, windowRole)) {
					success = False;
				}

				if(clientId) {
					XFree(clientId);
				}

				if(windowRole) {
					XFree(windowRole);
				}

				theWindow = theWindow->next;
			}
		}
	}

	prop1.name = SmRestartCommand;
	prop1.type = SmLISTofARRAY8;

	prop1.vals = calloc((Argc + 4), sizeof(SmPropValue));

	if(!prop1.vals) {
		success = False;
		goto bad;
	}

	numVals = 0;

	for(i = 0; i < Argc; i++) {
		if(strcmp(Argv[i], "-clientId") == 0 ||
		                strcmp(Argv[i], "-restore") == 0) {
			i++;
		}
		else {
			prop1.vals[numVals].value = (SmPointer) Argv[i];
			prop1.vals[numVals++].length = strlen(Argv[i]);
		}
	}

	prop1.vals[numVals].value = (SmPointer) "-clientId";
	prop1.vals[numVals++].length = 9;

	prop1.vals[numVals].value = (SmPointer) twm_clientId;
	prop1.vals[numVals++].length = strlen(twm_clientId);

	prop1.vals[numVals].value = (SmPointer) "-restore";
	prop1.vals[numVals++].length = 8;

	prop1.vals[numVals].value = (SmPointer) filename;
	prop1.vals[numVals++].length = strlen(filename);

	prop1.num_vals = numVals;

	sprintf(discardCommand, "rm %s", filename);
	prop2.name = SmDiscardCommand;
	prop2.type = SmARRAY8;
	prop2.num_vals = 1;
	prop2.vals = &prop2val;
	prop2val.value = (SmPointer) discardCommand;
	prop2val.length = strlen(discardCommand);

	props[0] = &prop1;
	props[1] = &prop2;

	SmcSetProperties(smcCon, 2, props);
	free(prop1.vals);

bad:
	SmcSaveYourselfDone(smcCon, success);
	sent_save_done = true;

	if(configFile) {
		fclose(configFile);
	}

	if(filename) {
		free(filename);
	}
}

/*===[ Save Yourself SM CallBack ]===========================================*/

static void SaveYourselfCB(SmcConn smcCon, SmPointer clientData,
                           int saveType, Bool shutdown, int interactStyle,
                           Bool fast)
/* this procedure is called by the session manager when requesting the
 * window manager to save its status, ie all the window configurations
 */
{
	if(!SmcRequestSaveYourselfPhase2(smcCon, SaveYourselfPhase2CB, NULL)) {
		SmcSaveYourselfDone(smcCon, False);
		sent_save_done = true;
	}
	else {
		sent_save_done = false;
	}
}

/*===[ Die SM Call Back ]====================================================*/

static void DieCB(SmcConn smcCon, SmPointer clientData)
/* this procedure is called by the session manager when requesting that the
 * application shut istelf down
 */
{
	SmcCloseConnection(smcCon, 0, NULL);
	XtRemoveInput(iceInputId);
	DoShutdown();
}

/*===[ Save Complete SM Call Back ]==========================================*/

static void SaveCompleteCB(SmcConn smcCon, SmPointer clientData)
/* This function is called to say that the save has been completed and that
 * the program can continue its operation
 */
{
	;
}

/*===[ Shutdown Cancelled SM Call Back ]=====================================*/

static void ShutdownCancelledCB(SmcConn smcCon, SmPointer clientData)

{
	if(!sent_save_done) {
		SmcSaveYourselfDone(smcCon, False);
		sent_save_done = true;
	}
}

/*===[ Process ICE Message ]=================================================*/

static void ProcessIceMsgProc(XtPointer client_data, int *source, XtInputId *id)

{
	IceConn     ice_conn = (IceConn) client_data;
	IceProcessMessages(ice_conn, NULL, NULL);
}


/*===[ Connect To Session Manager ]==========================================*/

void ConnectToSessionManager(char *previous_id)
/* This procedure attempts to connect to the session manager and setup the
 * interclent exchange via the Call Backs
 */
{
	char errorMsg[256];
	unsigned long mask;
	SmcCallbacks callbacks;
	IceConn iceConn;

	mask = SmcSaveYourselfProcMask | SmcDieProcMask |
	       SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask;

	callbacks.save_yourself.callback = SaveYourselfCB;
	callbacks.save_yourself.client_data = (SmPointer) NULL;

	callbacks.die.callback = DieCB;
	callbacks.die.client_data = (SmPointer) NULL;

	callbacks.save_complete.callback = SaveCompleteCB;
	callbacks.save_complete.client_data = (SmPointer) NULL;

	callbacks.shutdown_cancelled.callback = ShutdownCancelledCB;
	callbacks.shutdown_cancelled.client_data = (SmPointer) NULL;

	smcConn = SmcOpenConnection(
	                  NULL,                   /* use SESSION_MANAGER env */
	                  (SmPointer) appContext,
	                  SmProtoMajor,
	                  SmProtoMinor,
	                  mask,
	                  &callbacks,
	                  previous_id,
	                  &twm_clientId,
	                  256, errorMsg);

	if(smcConn == NULL) {
#ifdef DEBUG
		/* == [ Matthew McNeill Feb 1997 ] == */
		fprintf(stderr, "%s : Connection to session manager failed. (%s)", ProgramName,
		        errorMsg);
#endif
		return;
	}

	iceConn = SmcGetIceConnection(smcConn);

	iceInputId = XtAppAddInput(
	                     appContext,
	                     IceConnectionNumber(iceConn),
	                     (XtPointer) XtInputReadMask,
	                     ProcessIceMsgProc,
	                     (XtPointer) iceConn);
}


void shutdown_session(void)
{
	if(smcConn) {
		SmcCloseConnection(smcConn, 0, NULL);
	}
}
