/* session.c - session state handling and R6 style session management
 *
 *  Copyright (c) 1998-2003 Dan Pascu
 *  Copyright (c) 1998-2003 Alfredo Kojima
 *
 *  Window Maker window manager
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 *  USA.
 */


/*
 *
 * If defined(XSMP_ENABLED) and session manager is running then
 * 	do normal stuff
 * else
 * 	do pre-R6 session management stuff (save window state and relaunch)
 *
 * When doing a checkpoint:
 *
 * = Without XSMP
 * Open "Stop"/status Dialog
 * Send SAVE_YOURSELF to clients and wait for reply
 * Save restart info
 * Save state of clients
 *
 * = With XSMP
 * Send checkpoint request to sm
 *
 * When exiting:
 * -------------
 *
 * = Without XSMP
 *
 * Open "Exit Now"/status Dialog
 * Send SAVE_YOURSELF to clients and wait for reply
 * Save restart info
 * Save state of clients
 * Send DELETE to all clients
 * When no more clients are left or user hit "Exit Now", exit
 *
 * = With XSMP
 *
 * Send Shutdown request to session manager
 * if SaveYourself message received, save state of clients
 * if the Die message is received, exit.
 */

#include "wconfig.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>

#ifdef XSMP_ENABLED
#include <X11/SM/SMlib.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>


#include "WindowMaker.h"
#include "screen.h"
#include "window.h"
#include "client.h"
#include "session.h"
#include "wcore.h"
#include "framewin.h"
#include "workspace.h"
#include "funcs.h"
#include "properties.h"
#include "application.h"
#include "appicon.h"


#include "dock.h"


#include <WINGs/WUtil.h>

/** Global **/

extern Atom _XA_WM_SAVE_YOURSELF;

extern Time LastTimestamp;

#ifdef XSMP_ENABLED

extern int wScreenCount;

/* requested for SaveYourselfPhase2 */
static Bool sWaitingPhase2 = False;

static SmcConn sSMCConn = NULL;

static WMHandlerID sSMInputHandler = NULL;

/* our SM client ID */
static char *sClientID = NULL;
#endif


static WMPropList *sApplications = NULL;
static WMPropList *sCommand;
static WMPropList *sName;
static WMPropList *sHost;
static WMPropList *sWorkspace;
static WMPropList *sShaded;
static WMPropList *sMiniaturized;
static WMPropList *sHidden;
static WMPropList *sGeometry;
static WMPropList *sShortcutMask;

static WMPropList *sDock;

static WMPropList *sYes, *sNo;


static void
make_keys()
{
    if (sApplications!=NULL)
        return;

    sApplications = WMCreatePLString("Applications");
    sCommand = WMCreatePLString("Command");
    sName = WMCreatePLString("Name");
    sHost = WMCreatePLString("Host");
    sWorkspace = WMCreatePLString("Workspace");
    sShaded = WMCreatePLString("Shaded");
    sMiniaturized = WMCreatePLString("Miniaturized");
    sHidden = WMCreatePLString("Hidden");
    sGeometry = WMCreatePLString("Geometry");
    sDock = WMCreatePLString("Dock");
    sShortcutMask = WMCreatePLString("ShortcutMask");

    sYes = WMCreatePLString("Yes");
    sNo = WMCreatePLString("No");
}



static int
getBool(WMPropList *value)
{
    char *val;

    if (!WMIsPLString(value)) {
        return 0;
    }
    if (!(val = WMGetFromPLString(value))) {
        return 0;
    }

    if ((val[1]=='\0' && (val[0]=='y' || val[0]=='Y'))
        || strcasecmp(val, "YES")==0) {

        return 1;
    } else if ((val[1]=='\0' && (val[0]=='n' || val[0]=='N'))
               || strcasecmp(val, "NO")==0) {
        return 0;
    } else {
        int i;
        if (sscanf(val, "%i", &i)==1) {
            return (i!=0);
        } else {
            wwarning(_("can't convert \"%s\" to boolean"), val);
            return 0;
        }
    }
}


static unsigned
getInt(WMPropList *value)
{
    char *val;
    unsigned n;

    if (!WMIsPLString(value))
        return 0;
    val = WMGetFromPLString(value);
    if (!val)
        return 0;
    if (sscanf(val, "%u", &n) != 1)
        return 0;

    return n;
}



static WMPropList*
makeWindowState(WWindow *wwin, WApplication *wapp)
{
    WScreen *scr = wwin->screen_ptr;
    Window win;
    int i;
    unsigned mask;
    char *class, *instance, *command=NULL, buffer[512];
    WMPropList *win_state, *cmd, *name, *workspace;
    WMPropList *shaded, *miniaturized, *hidden, *geometry;
    WMPropList *dock, *shortcut;

    if (wwin->orig_main_window!=None && wwin->orig_main_window!=wwin->client_win)
        win = wwin->orig_main_window;
    else
        win = wwin->client_win;

    command = GetCommandForWindow(win);
    if (!command)
        return NULL;

    if (PropGetWMClass(win, &class, &instance)) {
        if (class && instance)
            snprintf(buffer, sizeof(buffer), "%s.%s", instance, class);
        else if (instance)
            snprintf(buffer, sizeof(buffer), "%s", instance);
        else if (class)
            snprintf(buffer, sizeof(buffer), ".%s", class);
        else
            snprintf(buffer, sizeof(buffer), ".");

        name = WMCreatePLString(buffer);
        cmd = WMCreatePLString(command);
        /*sprintf(buffer, "%d", wwin->frame->workspace+1);
         workspace = WMCreatePLString(buffer);*/
        workspace = WMCreatePLString(scr->workspaces[wwin->frame->workspace]->name);
        shaded = wwin->flags.shaded ? sYes : sNo;
        miniaturized = wwin->flags.miniaturized ? sYes : sNo;
        hidden = wwin->flags.hidden ? sYes : sNo;
        snprintf(buffer, sizeof(buffer), "%ix%i+%i+%i",
                 wwin->client.width, wwin->client.height,
                 wwin->frame_x, wwin->frame_y);
        geometry = WMCreatePLString(buffer);

        for (mask = 0, i = 0; i < MAX_WINDOW_SHORTCUTS; i++) {
            if (scr->shortcutWindows[i] != NULL &&
                WMGetFirstInArray(scr->shortcutWindows[i], wwin) != WANotFound) {
                mask |= 1<<i;
            }
        }

        snprintf(buffer, sizeof(buffer), "%u", mask);
        shortcut = WMCreatePLString(buffer);

        win_state = WMCreatePLDictionary(sName, name,
                                         sCommand, cmd,
                                         sWorkspace, workspace,
                                         sShaded, shaded,
                                         sMiniaturized, miniaturized,
                                         sHidden, hidden,
                                         sShortcutMask, shortcut,
                                         sGeometry, geometry,
                                         NULL);

        WMReleasePropList(name);
        WMReleasePropList(cmd);
        WMReleasePropList(workspace);
        WMReleasePropList(geometry);
        WMReleasePropList(shortcut);
        if (wapp && wapp->app_icon && wapp->app_icon->dock) {
            int i;
            char *name;
            if (wapp->app_icon->dock == scr->dock) {
                name="Dock";
            } else {
                for(i=0; i<scr->workspace_count; i++)
                    if(scr->workspaces[i]->clip == wapp->app_icon->dock)
                        break;
                assert(i < scr->workspace_count);
                /*n = i+1;*/
                name = scr->workspaces[i]->name;
            }
            dock = WMCreatePLString(name);
            WMPutInPLDictionary(win_state, sDock, dock);
            WMReleasePropList(dock);
        }
    } else {
        win_state = NULL;
    }

    if (instance) XFree(instance);
    if (class) XFree(class);
    if (command) wfree(command);

    return win_state;
}


void
wSessionSaveState(WScreen *scr)
{
    WWindow *wwin = scr->focused_window;
    WMPropList *win_info, *wks;
    WMPropList *list=NULL;
    WMArray *wapp_list=NULL;


    make_keys();

    if (!scr->session_state) {
        scr->session_state = WMCreatePLDictionary(NULL, NULL);
        if (!scr->session_state)
            return;
    }

    list = WMCreatePLArray(NULL);

    wapp_list = WMCreateArray(16);

    while (wwin) {
        WApplication *wapp=wApplicationOf(wwin->main_window);
        Window appId = wwin->orig_main_window;

        if ((wwin->transient_for==None
             || wwin->transient_for==wwin->screen_ptr->root_win)
            && WMGetFirstInArray(wapp_list, (void*)appId)==WANotFound
            && !WFLAGP(wwin, dont_save_session)) {
            /* A entry for this application was not yet saved. Save one. */
            if ((win_info = makeWindowState(wwin, wapp))!=NULL) {
                WMAddToPLArray(list, win_info);
                WMReleasePropList(win_info);
                /* If we were succesful in saving the info for this window
                 * add the application the window belongs to, to the
                 * application list, so no multiple entries for the same
                 * application are saved.
                 */
                WMAddToArray(wapp_list, (void*)appId);
            }
        }
        wwin = wwin->prev;
    }
    WMRemoveFromPLDictionary(scr->session_state, sApplications);
    WMPutInPLDictionary(scr->session_state, sApplications, list);
    WMReleasePropList(list);

    wks = WMCreatePLString(scr->workspaces[scr->current_workspace]->name);
    WMPutInPLDictionary(scr->session_state, sWorkspace, wks);
    WMReleasePropList(wks);

    WMFreeArray(wapp_list);
}


void
wSessionClearState(WScreen *scr)
{
    make_keys();

    if (!scr->session_state)
        return;

    WMRemoveFromPLDictionary(scr->session_state, sApplications);
    WMRemoveFromPLDictionary(scr->session_state, sWorkspace);
}


static pid_t
execCommand(WScreen *scr, char *command, char *host)
{
    pid_t pid;
    char **argv;
    int argc;

    wtokensplit(command, &argv, &argc);

    if (argv==NULL) {
        return 0;
    }

    if ((pid=fork())==0) {
        char **args;
        int i;

        SetupEnvironment(scr);

        args = malloc(sizeof(char*)*(argc+1));
        if (!args)
            exit(111);
        for (i=0; i<argc; i++) {
            args[i] = argv[i];
        }
        args[argc] = NULL;
        execvp(argv[0], args);
        exit(111);
    }
    while (argc > 0)
        wfree(argv[--argc]);
    wfree(argv);
    return pid;
}


static WSavedState*
getWindowState(WScreen *scr, WMPropList *win_state)
{
    WSavedState *state = wmalloc(sizeof(WSavedState));
    WMPropList *value;
    char *tmp;
    unsigned mask;
    int i;

    memset(state, 0, sizeof(WSavedState));
    state->workspace = -1;
    value = WMGetFromPLDictionary(win_state, sWorkspace);
    if (value && WMIsPLString(value)) {
        tmp = WMGetFromPLString(value);
        if (sscanf(tmp, "%i", &state->workspace)!=1) {
            state->workspace = -1;
            for (i=0; i < scr->workspace_count; i++) {
                if (strcmp(scr->workspaces[i]->name, tmp)==0) {
                    state->workspace = i;
                    break;
                }
            }
        } else {
            state->workspace--;
        }
    }
    if ((value = WMGetFromPLDictionary(win_state, sShaded))!=NULL)
        state->shaded = getBool(value);
    if ((value = WMGetFromPLDictionary(win_state, sMiniaturized))!=NULL)
        state->miniaturized = getBool(value);
    if ((value = WMGetFromPLDictionary(win_state, sHidden))!=NULL)
        state->hidden = getBool(value);
    if ((value = WMGetFromPLDictionary(win_state, sShortcutMask))!=NULL) {
        mask = getInt(value);
        state->window_shortcuts = mask;
    }

    value = WMGetFromPLDictionary(win_state, sGeometry);
    if (value && WMIsPLString(value)) {
        if (!(sscanf(WMGetFromPLString(value), "%ix%i+%i+%i",
                     &state->w, &state->h, &state->x, &state->y)==4 &&
              (state->w>0 && state->h>0))) {
            state->w = 0;
            state->h = 0;
        }
    }

    return state;
}


#define SAME(x, y) (((x) && (y) && !strcmp((x), (y))) || (!(x) && !(y)))

void
wSessionRestoreState(WScreen *scr)
{
    WSavedState *state;
    char *instance, *class, *command, *host;
    WMPropList *win_info, *apps, *cmd, *value;
    pid_t pid;
    int i, count;
    WDock *dock;
    WAppIcon *btn=NULL;
    int j, n, found;
    char *tmp;

    make_keys();

    if (!scr->session_state)
        return;

    WMPLSetCaseSensitive(True);

    apps = WMGetFromPLDictionary(scr->session_state, sApplications);
    if (!apps)
        return;

    count = WMGetPropListItemCount(apps);
    if (count==0)
        return;

    for (i=0; i<count; i++) {
        win_info = WMGetFromPLArray(apps, i);

        cmd = WMGetFromPLDictionary(win_info, sCommand);
        if (!cmd || !WMIsPLString(cmd) || !(command = WMGetFromPLString(cmd))) {
            continue;
        }

        value = WMGetFromPLDictionary(win_info, sName);
        if (!value)
            continue;

        ParseWindowName(value, &instance, &class, "session");
        if (!instance && !class)
            continue;

        value = WMGetFromPLDictionary(win_info, sHost);
        if (value && WMIsPLString(value))
            host = WMGetFromPLString(value);
        else
            host = NULL;

        state = getWindowState(scr, win_info);

        dock = NULL;
        value = WMGetFromPLDictionary(win_info, sDock);
        if (value && WMIsPLString(value) && (tmp = WMGetFromPLString(value))!=NULL) {
            if (sscanf(tmp, "%i", &n)!=1) {
                if (!strcasecmp(tmp, "DOCK")) {
                    dock = scr->dock;
                } else {
                    for (j=0; j < scr->workspace_count; j++) {
                        if (strcmp(scr->workspaces[j]->name, tmp)==0) {
                            dock = scr->workspaces[j]->clip;
                            break;
                        }
                    }
                }
            } else {
                if (n == 0) {
                    dock = scr->dock;
                } else if (n>0 && n<=scr->workspace_count) {
                    dock = scr->workspaces[n-1]->clip;
                }
            }
        }

        found = 0;
        if (dock!=NULL) {
            for (j=0; j<dock->max_icons; j++) {
                btn = dock->icon_array[j];
                if (btn && SAME(instance, btn->wm_instance) &&
                    SAME(class, btn->wm_class) &&
                    SAME(command, btn->command) &&
                    !btn->launching) {
                    found = 1;
                    break;
                }
            }
        }

        if (found) {
            wDockLaunchWithState(dock, btn, state);
        } else if ((pid = execCommand(scr, command, host)) > 0) {
            wWindowAddSavedState(instance, class, command, pid, state);
        } else {
            wfree(state);
        }

        if (instance) wfree(instance);
        if (class) wfree(class);
    }
    /* clean up */
    WMPLSetCaseSensitive(False);
}


void
wSessionRestoreLastWorkspace(WScreen *scr)
{
    WMPropList *wks;
    int w, i;
    char *tmp;

    make_keys();

    if (!scr->session_state)
        return;

    WMPLSetCaseSensitive(True);

    wks = WMGetFromPLDictionary(scr->session_state, sWorkspace);
    if (!wks || !WMIsPLString(wks))
        return;

    tmp = WMGetFromPLString(wks);

    /* clean up */
    WMPLSetCaseSensitive(False);

    if (sscanf(tmp, "%i", &w)!=1) {
        w = -1;
        for (i=0; i < scr->workspace_count; i++) {
            if (strcmp(scr->workspaces[i]->name, tmp)==0) {
                w = i;
                break;
            }
        }
    } else {
        w--;
    }

    if (w!=scr->current_workspace && w<scr->workspace_count) {
        wWorkspaceChange(scr, w);
    }
}


static void
clearWaitingAckState(WScreen *scr)
{
    WWindow *wwin;
    WApplication *wapp;

    for (wwin = scr->focused_window; wwin != NULL; wwin = wwin->prev) {
        wwin->flags.waiting_save_ack = 0;
        if (wwin->main_window != None) {
            wapp = wApplicationOf(wwin->main_window);
            if (wapp)
                wapp->main_window_desc->flags.waiting_save_ack = 0;
        }
    }
}


void
wSessionSaveClients(WScreen *scr)
{

}


/*
 * With XSMP, this job is done by smproxy
 */
void
wSessionSendSaveYourself(WScreen *scr)
{
    WWindow *wwin;
    int count;

    /* freeze client interaction with clients */
    XGrabKeyboard(dpy, scr->root_win, False, GrabModeAsync, GrabModeAsync,
                  CurrentTime);
    XGrabPointer(dpy, scr->root_win, False, ButtonPressMask|ButtonReleaseMask,
                 GrabModeAsync, GrabModeAsync, scr->root_win, None,
                 CurrentTime);

    clearWaitingAckState(scr);

    count = 0;

    /* first send SAVE_YOURSELF for everybody */
    for (wwin = scr->focused_window; wwin != NULL; wwin = wwin->prev) {
        WWindow *mainWin;

        mainWin = wWindowFor(wwin->main_window);

        if (mainWin) {
            /* if the client is a multi-window client, only send message
             * to the main window */
            wwin = mainWin;
        }

        /* make sure the SAVE_YOURSELF flag is up-to-date */
        PropGetProtocols(wwin->client_win, &wwin->protocols);

        if (wwin->protocols.SAVE_YOURSELF) {
            if (!wwin->flags.waiting_save_ack) {
                wClientSendProtocol(wwin, _XA_WM_SAVE_YOURSELF, LastTimestamp);

                wwin->flags.waiting_save_ack = 1;
                count++;
            }
        } else {
            wwin->flags.waiting_save_ack = 0;
        }
    }

    /* then wait for acknowledge */
    while (count > 0) {

    }

    XUngrabPointer(dpy, CurrentTime);
    XUngrabKeyboard(dpy, CurrentTime);
    XFlush(dpy);
}


#ifdef XSMP_ENABLED
/*
 * With full session management support, the part of WMState
 * that store client window state will become obsolete (maybe we can reuse
 *							the old code too),
 * but we still need to store state info like the dock and workspaces.
 * It is better to keep dock/wspace info in WMState because the user
 * might want to keep the dock configuration while not wanting to
 * resume a previously saved session.
 * So, wmaker specific state info can be saved in
 * ~/GNUstep/Library/WindowMaker/statename.state
 * Its better to not put it in the defaults directory because:
 * - its not a defaults file (having domain names like wmaker0089504baa
 * in the defaults directory wouldn't be very neat)
 * - this state file is not meant to be edited by users
 *
 * The old session code will become obsolete. When wmaker is
 * compiled with R6 sm support compiled in, it'll be better to
 * use a totally rewritten state saving code, but we can keep
 * the current code for when XSMP_ENABLED is not compiled in.
 *
 * This will be confusing to old users (well get lots of "SAVE_SESSION broke!"
 * messages), but it'll be better.
 *
 * -readme
 */


static char*
getWindowRole(Window window)
{
    XTextProperty prop;
    static Atom atom = 0;

    if (!atom)
        atom = XInternAtom(dpy, "WM_WINDOW_ROLE", False);

    if (XGetTextProperty(dpy, window, &prop, atom)) {
        if (prop.encoding == XA_STRING && prop.format == 8 && prop.nitems > 0)
            return prop.value;
    }

    return NULL;
}


/*
 *
 * Saved Info:
 *
 * WM_WINDOW_ROLE
 *
 * WM_CLASS.instance
 * WM_CLASS.class
 * WM_NAME
 * WM_COMMAND
 *
 * geometry
 * state = (miniaturized, shaded, etc)
 * attribute
 * workspace #
 * app state = (which dock, hidden)
 * window shortcut #
 */

static WMPropList*
makeAppState(WWindow *wwin)
{
    WApplication *wapp;
    WMPropList *state;
    WScreen *scr = wwin->screen_ptr;

    state = WMCreatePLArray(NULL, NULL);

    wapp = wApplicationOf(wwin->main_window);

    if (wapp) {
        if (wapp->app_icon && wapp->app_icon->dock) {

            if (wapp->app_icon->dock == scr->dock) {
                WMAddToPLArray(state, WMCreatePLString("Dock"));
            } else {
                int i;

                for(i=0; i<scr->workspace_count; i++)
                    if(scr->workspaces[i]->clip == wapp->app_icon->dock)
                        break;

                assert(i < scr->workspace_count);

                WMAddToPLArray(state,
                               WMCreatePLString(scr->workspaces[i]->name));
            }
        }

        WMAddToPLArray(state, WMCreatePLString(wapp->hidden ? "1" : "0"));
    }

    return state;
}



Bool
wSessionGetStateFor(WWindow *wwin, WSessionData *state)
{
    char *str;
    WMPropList *slist;
    WMPropList *elem;
    WMPropList *value;
    int index = 0;

    index = 3;

    /* geometry */
    value = WMGetFromPLArray(slist, index++);
    str = WMGetFromPLString(value);

    sscanf(str, "%i %i %i %i %i %i", &state->x, &state->y,
           &state->width, &state->height,
           &state->user_changed_width, &state->user_changed_height);


    /* state */
    value = WMGetFromPLArray(slist, index++);
    str = WMGetFromPLString(value);

    sscanf(str, "%i %i %i", &state->miniaturized, &state->shaded,
           &state->maximized);


    /* attributes */
    value = WMGetFromPLArray(slist, index++);
    str = WMGetFromPLString(value);

    getAttributeState(str, &state->mflags, &state->flags);


    /* workspace */
    value = WMGetFromPLArray(slist, index++);
    str = WMGetFromPLString(value);

    sscanf(str, "%i", &state->workspace);


    /* app state (repeated for all windows of the app) */
    value = WMGetFromPLArray(slist, index++);
    str = WMGetFromPLString(value);

    /* ???? */

    /* shortcuts */
    value = WMGetFromPLArray(slist, index++);
    str = WMGetFromPLString(value);

    sscanf(str, "%i", &state->shortcuts);
}



static WMPropList*
makeAttributeState(WWindow *wwin)
{
    unsigned int data1, data2;
    char buffer[256];

#define W_FLAG(wwin, FLAG)	((wwin)->defined_user_flags.FLAG \
    ? (wwin)->user_flags.FLAG : -1)

    snprintf(buffer, sizeof(buffer),
             "%i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i %i",
             W_FLAG(no_titlebar),
             W_FLAG(no_resizable),
             W_FLAG(no_closable),
             W_FLAG(no_miniaturizable),
             W_FLAG(no_resizebar),
             W_FLAG(no_close_button),
             W_FLAG(no_miniaturize_button),
             /*
              W_FLAG(broken_close),
              W_FLAG(kill_close),
              */
             W_FLAG(no_shadeable),
             W_FLAG(omnipresent),
             W_FLAG(skip_window_list),
             W_FLAG(floating),
             W_FLAG(sunken),
             W_FLAG(no_bind_keys),
             W_FLAG(no_bind_mouse),
             W_FLAG(no_hide_others),
             W_FLAG(no_appicon),
             W_FLAG(dont_move_off),
             W_FLAG(no_focusable),
             W_FLAG(always_user_icon),
             W_FLAG(start_miniaturized),
             W_FLAG(start_hidden),
             W_FLAG(start_maximized),
             W_FLAG(dont_save_session),
             W_FLAG(emulate_appicon));

    return WMCreatePLString(buffer);
}


static void
appendStringInArray(WMPropList *array, char *str)
{
    WMPropList *val;

    val = WMCreatePLString(str);
    WMAddToPLArray(array, val);
    WMReleasePropList(val);
}


static WMPropList*
makeClientState(WWindow *wwin)
{
    WMPropList *state;
    WMPropList *tmp;
    char *str;
    char buffer[512];
    int i;
    unsigned shortcuts;

    state = WMCreatePLArray(NULL, NULL);

    /* WM_WINDOW_ROLE */
    str = getWindowRole(wwin->client_win);
    if (!str)
        appendStringInArray(state, "");
    else {
        appendStringInArray(state, str);
        XFree(str);
    }

    /* WM_CLASS.instance */
    appendStringInArray(state, wwin->wm_instance);

    /* WM_CLASS.class */
    appendStringInArray(state, wwin->wm_class);

    /* WM_NAME */
    if (wwin->flags.wm_name_changed)
        appendStringInArray(state, "");
    else
        appendStringInArray(state, wwin->frame->name);

    /* geometry */
    snprintf(buffer, sizeof(buffer), "%i %i %i %i %i %i", wwin->frame_x, wwin->frame_y,
             wwin->client.width, wwin->client.height,
             wwin->flags.user_changed_width, wwin->flags.user_changed_height);
    appendStringInArray(state, buffer);

    /* state */
    snprintf(buffer, sizeof(buffer), "%i %i %i", wwin->flags.miniaturized,
             wwin->flags.shaded, wwin->flags.maximized);
    appendStringInArray(state, buffer);

    /* attributes */
    tmp = makeAttributeState(wwin);
    WMAddToPLArray(state, tmp);
    WMReleasePropList(tmp);

    /* workspace */
    snprintf(buffer, sizeof(buffer), "%i", wwin->frame->workspace);
    appendStringInArray(state, buffer);

    /* app state (repeated for all windows of the app) */
    tmp = makeAppState(wwin);
    WMAddToPLArray(state, tmp);
    WMReleasePropList(tmp);

    /* shortcuts */
    shortcuts = 0;
    for (i = 0; i < MAX_WINDOW_SHORTCUTS; i++) {
        if (scr->shortcutWindow[i] == wwin) {
            shortcuts |= 1 << i;
        }
    }
    snprintf(buffer, sizeof(buffer), "%ui", shortcuts);
    appendStringInArray(tmp, buffer);

    return state;
}


static void
smSaveYourselfPhase2Proc(SmcConn smc_conn, SmPointer client_data)
{
    SmProp props[4];
    SmPropValue prop1val, prop2val, prop3val, prop4val;
    char **argv = (char**)client_data;
    int argc;
    int i, j;
    Bool ok = False;
    char *statefile = NULL;
    char *prefix;
    Bool gsPrefix = False;
    char *discardCmd = NULL;
    time_t t;
    WMPropList *state, *plState;
    int len;

#ifdef DEBUG1
    puts("received SaveYourselfPhase2 SM message");
#endif

    /* save session state */

    /* the file that will contain the state */
    prefix = getenv("SM_SAVE_DIR");
    if (!prefix) {
        prefix = wusergnusteppath();
        if (prefix)
            gsPrefix = True;
    }
    if (!prefix) {
        prefix = getenv("HOME");
    }
    if (!prefix)
        prefix = ".";

    len = strlen(prefix)+64;
    statefile = malloc(len);
    if (!statefile) {
        wwarning(_("out of memory while saving session state"));
        goto fail;
    }

    t = time();
    i = 0;
    do {
        if (gsPrefix)
            snprintf(statefile, len, "%s/Library/WindowMaker/wmaker.%l%i.state",
                     prefix, t, i);
        else
            snprintf(statefile, len, "%s/wmaker.%l%i.state", prefix, t, i);
        i++;
    } while (access(F_OK, statefile)!=-1);

    /* save the states of all windows we're managing */
    state = WMCreatePLArray(NULL, NULL);

    /*
     * Format:
     *
     * state_file ::= dictionary with version_info ; state
     * version_info ::= 'version' = '1';
     * state ::= 'state' = array of screen_info
     * screen_info ::= array of (screen number, window_info, window_info, ...)
     * window_info ::=
     */
    for (i=0; i<wScreenCount; i++) {
        WScreen *scr;
        WWindow *wwin;
        char buf[32];
        WMPropList *pscreen;

        scr = wScreenWithNumber(i);

        snprintf(buf, sizeof(buf), "%i", scr->screen);
        pscreen = WMCreatePLArray(WMCreatePLString(buf), NULL);

        wwin = scr->focused_window;
        while (wwin) {
            WMPropList *pwindow;

            pwindow = makeClientState(wwin);
            WMAddToPLArray(pscreen, pwindow);

            wwin = wwin->prev;
        }

        WMAddToPLArray(state, pscreen);
    }

    plState = WMCreatePLDictionary(WMCreatePLString("Version"),
                                   WMCreatePLString("1.0"),
                                   WMCreatePLString("Screens"),
                                   state, NULL);

    WMWritePropListToFile(plState, statefile, False);

    WMReleasePropList(plState);

    /* set the remaining properties that we didn't set at
     * startup time */

    for (argc=0, i=0; argv[i]!=NULL; i++) {
        if (strcmp(argv[i], "-clientid")==0
            || strcmp(argv[i], "-restore")==0) {
            i++;
        } else {
            argc++;
        }
    }

    prop[0].name = SmRestartCommand;
    prop[0].type = SmLISTofARRAY8;
    prop[0].vals = malloc(sizeof(SmPropValue)*(argc+4));
    prop[0].num_vals = argc+4;

    prop[1].name = SmCloneCommand;
    prop[1].type = SmLISTofARRAY8;
    prop[1].vals = malloc(sizeof(SmPropValue)*(argc));
    prop[1].num_vals = argc;

    if (!prop[0].vals || !prop[1].vals) {
        wwarning(_("end of memory while saving session state"));
        goto fail;
    }

    for (j=0, i=0; i<argc+4; i++) {
        if (strcmp(argv[i], "-clientid")==0
            || strcmp(argv[i], "-restore")==0) {
            i++;
        } else {
            prop[0].vals[j].value = argv[i];
            prop[0].vals[j].length = strlen(argv[i]);
            prop[1].vals[j].value = argv[i];
            prop[1].vals[j].length = strlen(argv[i]);
            j++;
        }
    }
    prop[0].vals[j].value = "-clientid";
    prop[0].vals[j].length = 9;
    j++;
    prop[0].vals[j].value = sClientID;
    prop[0].vals[j].length = strlen(sClientID);
    j++;
    prop[0].vals[j].value = "-restore";
    prop[0].vals[j].length = 11;
    j++;
    prop[0].vals[j].value = statefile;
    prop[0].vals[j].length = strlen(statefile);

    {
        int len = strlen(statefile)+8;

        discardCmd = malloc(len);
        if (!discardCmd)
            goto fail;
        snprintf(discardCmd, len, "rm %s", statefile);
    }
    prop[2].name = SmDiscardCommand;
    prop[2].type = SmARRAY8;
    prop[2].vals[0] = discardCmd;
    prop[2].num_vals = 1;

    SmcSetProperties(sSMCConn, 3, prop);

    ok = True;
fail:
    SmcSaveYourselfDone(smc_conn, ok);

    if (prop[0].vals)
        wfree(prop[0].vals);
    if (prop[1].vals)
        wfree(prop[1].vals);
    if (discardCmd)
        wfree(discardCmd);

    if (!ok) {
        remove(statefile);
    }
    if (statefile)
        wfree(statefile);
}


static void
smSaveYourselfProc(SmcConn smc_conn, SmPointer client_data, int save_type,
                   Bool shutdown, int interact_style, Bool fast)
{
#ifdef DEBUG1
    puts("received SaveYourself SM message");
#endif

    if (!SmcRequestSaveYourselfPhase2(smc_conn, smSaveYourselfPhase2Proc,
                                      client_data)) {

        SmcSaveYourselfDone(smc_conn, False);
        sWaitingPhase2 = False;
    } else {
#ifdef DEBUG1
        puts("successfull request of SYS phase 2");
#endif
        sWaitingPhase2 = True;
    }
}


static void
smDieProc(SmcConn smc_conn, SmPointer client_data)
{
#ifdef DEBUG1
    puts("received Die SM message");
#endif

    wSessionDisconnectManager();

    Shutdown(WSExitMode, True);
}



static void
smSaveCompleteProc(SmcConn smc_conn)
{
    /* it means that we can resume doing things that can change our state */
#ifdef DEBUG1
    puts("received SaveComplete SM message");
#endif
}


static void
smShutdownCancelledProc(SmcConn smc_conn, SmPointer client_data)
{
    if (sWaitingPhase2) {

        sWaitingPhase2 = False;

        SmcSaveYourselfDone(smc_conn, False);
    }
}


static void
iceMessageProc(int fd, int mask, void *clientData)
{
    IceConn iceConn = (IceConn)clientData;

    IceProcessMessages(iceConn, NULL, NULL);
}


static void
iceIOErrorHandler(IceConnection ice_conn)
{
    /* This is not fatal but can mean the session manager exited.
     * If the session manager exited normally we would get a
     * Die message, so this probably means an abnormal exit.
     * If the sm was the last client of session, then we'll die
     * anyway, otherwise we can continue doing our stuff.
     */
    wwarning(_("connection to the session manager was lost"));
    wSessionDisconnectManager();
}


void
wSessionConnectManager(char **argv, int argc)
{
    IceConn iceConn;
    char *previous_id = NULL;
    char buffer[256];
    SmcCallbacks callbacks;
    unsigned long mask;
    char uid[32];
    char pid[32];
    SmProp props[4];
    SmPropValue prop1val, prop2val, prop3val, prop4val;
    char restartStyle;
    int i;

    mask = SmcSaveYourselfProcMask|SmcDieProcMask|SmcSaveCompleteProcMask
        |SmcShutdownCancelledProcMask;

    callbacks.save_yourself.callback = smSaveYourselfProc;
    callbacks.save_yourself.client_data = argv;

    callbacks.die.callback = smDieProc;
    callbacks.die.client_data = NULL;

    callbacks.save_complete.callback = smSaveCompleteProc;
    callbacks.save_complete.client_data = NULL;

    callbacks.shutdown_cancelled.callback = smShutdownCancelledProc;
    callbacks.shutdown_cancelled.client_data = NULL;

    for (i=0; i<argc; i++) {
        if (strcmp(argv[i], "-clientid")==0) {
            previous_id = argv[i+1];
            break;
        }
    }

    /* connect to the session manager */
    sSMCConn = SmcOpenConnection(NULL, NULL, SmProtoMajor, SmProtoMinor,
                                 mask, &callbacks, previous_id,
                                 &sClientID, 255, buffer);
    if (!sSMCConn) {
        return;
    }
#ifdef DEBUG1
    puts("connected to the session manager");
#endif

    /*    IceSetIOErrorHandler(iceIOErrorHandler);*/

    /* check for session manager clients */
    iceConn = SmcGetIceConnection(smcConn);

    if (fcntl(IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC) < 0) {
        wsyserror("error setting close-on-exec flag for ICE connection");
    }

    sSMInputHandler = WMAddInputHandler(IceConnectionNumber(iceConn),
                                        WIReadMask, iceMessageProc, iceConn);

    /* setup information about ourselves */

    /* program name */
    prop1val.value = argv[0];
    prop1val.length = strlen(argv[0]);
    prop[0].name = SmProgram;
    prop[0].type = SmARRAY8;
    prop[0].num_vals = 1;
    prop[0].vals = &prop1val;

    /* The XSMP doc from X11R6.1 says it contains the user name,
     * but every client implementation I saw places the uid # */
    snprintf(uid, sizeof(uid), "%i", getuid());
    prop2val.value = uid;
    prop2val.length = strlen(uid);
    prop[1].name = SmUserID;
    prop[1].type = SmARRAY8;
    prop[1].num_vals = 1;
    prop[1].vals = &prop2val;

    /* Restart style. We should restart only if we were running when
     * the previous session finished. */
    restartStyle = SmRestartIfRunning;
    prop3val.value = &restartStyle;
    prop3val.length = 1;
    prop[2].name = SmRestartStyleHint;
    prop[2].type = SmCARD8;
    prop[2].num_vals = 1;
    prop[2].vals = &prop3val;

    /* Our PID. Not required but might be usefull */
    snprintf(pid, sizeof(pid), "%i", getpid());
    prop4val.value = pid;
    prop4val.length = strlen(pid);
    prop[3].name = SmProcessID;
    prop[3].type = SmARRAY8;
    prop[3].num_vals = 1;
    prop[3].vals = &prop4val;

    /* we'll set the rest of the hints later */

    SmcSetProperties(sSMCConn, 4, props);

}


void
wSessionDisconnectManager(void)
{
    if (sSMCConn) {
        WMDeleteInputHandler(sSMInputHandler);
        sSMInputHandler = NULL;

        SmcCloseConnection(sSMCConn, 0, NULL);
        sSMCConn = NULL;
    }
}

void
wSessionRequestShutdown(void)
{
    /* request a shutdown to the session manager */
    if (sSMCConn)
        SmcRequestSaveYourself(sSMCConn, SmSaveBoth, True, SmInteractStyleAny,
                               False, True);
}


Bool
wSessionIsManaged(void)
{
    return sSMCConn!=NULL;
}

#endif /* !XSMP_ENABLED */



syntax highlighted by Code2HTML, v. 0.9.1