
/*
 *@@sourcefile xshutdwn.c:
 *      this file contains all the XShutdown code, which
 *      was in xfdesk.c before V0.84.
 *
 *      All the functions in this file have the xsd* prefix.
 *
 *      XShutdown is started from xfdesk.c simply by creating
 *      the Shutdown thread, which will then take over.
 *      See xsd_fntShutdownThread for a detailed description.
 *
 *@@include #define INCL_DOSPROCESS
 *@@include #define INCL_DOSSEMAPHORES
 *@@include #define INCL_WINWINDOWMGR
 *@@include #define INCL_WINPOINTERS
 *@@include #define INCL_WINSWITCHLIST
 *@@include #include <os2.h>
 *@@include #include <wpobject.h>
 *@@include #include "xshutdwn.h"
 */

/*
 *      Copyright (C) 1997-99 Ulrich M”ller.
 *      This file is part of the XFolder source package.
 *      XFolder 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, in version 2 as it comes in the
 *      "COPYING" file of the XFolder main distribution.
 *      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.
 */

/*
 *  Suggested #include order:
 *  1)  os2.h
 *  2)  C library headers
 *  3)  SOM headers which work with precompiled header files
 *  4)  headers in /helpers
 *  5)  headers in /main with dlgids.h and common.h first
 *  6)  #pragma hdrstop to prevent VAC++ crashes
 *  7)  other needed SOM headers
 *  8)  for non-SOM-class files: corresponding header (e.g. classlst.h)
 */

#define INCL_DOSPROCESS         // DosSleep, priorities, PIDs etc.
#define INCL_DOSSEMAPHORES      // needed for xthreads.h
#define INCL_DOSEXCEPTIONS      // needed for except.h
#define INCL_DOSERRORS
#define INCL_DOSDEVICES
#define INCL_DOSDEVIOCTL

#define INCL_WINWINDOWMGR
#define INCL_WINFRAMEMGR        // WM_FORMATFRAME, SC_CLOSE etc.
#define INCL_WINPOINTERS

#define INCL_WINCOUNTRY         // WinCompareStrings, WinUpper

#define INCL_WINDIALOGS
#define INCL_WINSTATICS
#define INCL_WINMENUS           // needed for menus.h
#define INCL_WINENTRYFIELDS
#define INCL_WINBUTTONS
#define INCL_WINLISTBOXES

#define INCL_WINSTDCNR          // needed for winh.h

#define INCL_WINSHELLDATA       // profile funcs
#define INCL_WINSWITCHLIST
#define INCL_WINPROGRAMLIST     // needed for WPProgram

#include <os2.h>

// C library headers
#include <stdio.h>              // needed for except.h
#include <setjmp.h>             // needed for except.h
#include <assert.h>             // needed for except.h

// headers in /helpers
#include "dosh.h"               // Control Program helper routines
#include "winh.h"               // PM helper routines
#include "prfh.h"               // INI file helper routines

#include "animate.h"            // icon and other animations
#include "linklist.h"           // linked list helper routines
#include "procstat.h"           // DosQProcStat handling
#include "progbars.h"           // progress bar control

// SOM headers which don't crash with prec. header files
#include "xfldr.h"              // needed for shutdown folder

// headers in /main
#include "dlgids.h"             // all the IDs that are shared with NLS
#include "common.h"             // the majestic XFolder include file

#include "apm.h"                // APM power-off for XShutdown
#include "except.h"             // XFolder exception handling
#include "menus.h"              // common XFolder context menu logic
#include "module.h"             // XFolder main DLL information
#include "notebook.h"           // generic XFolder notebook handling
#include "sound.h"              // declarations for SOUND.DLL
#include "xthreads.h"           // XFolder threads; this includes threads.h

// other SOM headers
#pragma hdrstop                 // VAC++ keeps crashing otherwise
#include <wpdesk.h>             // WPDesktop

#include "xwps.h"               // XFolder pseudo SOM functions

// finally, our own header file
#include "xshutdwn.h"           // XFolder eXtended Shutdown

/* ******************************************************************
 *                                                                  *
 *   Global variables                                               *
 *                                                                  *
 ********************************************************************/

HWND                hwndMain = NULLHANDLE,
                    hwndShutdownStatus = NULLHANDLE;
PFNWP               SysWndProc;
BOOL                fAllWindowsClosed = FALSE,
                    fClosingApps = FALSE,
                    fShutdownBegun = FALSE;

PID                 pidWPS, pidPM;
ULONG               sidWPS, sidPM;

ULONG               ulMaxItemCount,
                    ulLastItemCount;

HFILE               hfShutdownLog = NULLHANDLE;

// shutdown animation
SHUTDOWNANIM         sdAnim;

/*
 *  here come the necessary definitions and functions
 *  for maintaining pliShutdownFirst, the global list of
 *  windows which need to be closed; these lists are
 *  maintained according to the system Tasklist
 */

// this is the pointer to the shutdown params
// passed in the ulData parameter in the
// THREADINFO structure
PSHUTDOWNPARAMS     psdParams = NULL;

// this is the global list of items to be closed
PSHUTLISTITEM       pliShutdownFirst = NULL,
                    pliShutdownLast = NULL,
                    pliSkipped = NULL;
HMTX                hmtxShutdown = NULLHANDLE,
                    hmtxSkipped = NULLHANDLE;
HEV                 hevUpdated = NULLHANDLE;

// temporary storage for closing VIOs
HWND                hwndVioDlg = NULLHANDLE;
CHAR                szVioTitle[1000] = "";
SHUTLISTITEM        VioItem;

PAUTOCLOSELISTITEM  pliAutoCloseFirst = NULL;

WPDesktop            *pActiveDesktop = NULL;
HWND                 hwndActiveDesktop = NULLHANDLE;

// forward declarations
MRESULT EXPENTRY fnwpShutdown(HWND hwndFrame, ULONG msg, MPARAM mp1, MPARAM mp2);
void _Optlink xsd_fntUpdateThread(PVOID ptiMyself);
VOID xsdFinishShutdown(VOID);
VOID xsdFinishStandardMessage(VOID);
VOID xsdFinishStandardReboot(VOID);
VOID xsdFinishUserReboot(VOID);
VOID xsdFinishAPMPowerOff(VOID);

/* ******************************************************************
 *                                                                  *
 *   Shutdown helper functions                                      *
 *                                                                  *
 ********************************************************************/

/*
 *@@ xsdLoadAnimation:
 *      this loads the shutdown (traffic light) animation
 *      as an array of icons from the XFLDR.DLL module.
 */

VOID xsdLoadAnimation(PSHUTDOWNANIM psda)
{
    HMODULE hmod = modQueryHandle();
    (psda->ahptr)[0] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM1);
    (psda->ahptr)[1] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM2);
    (psda->ahptr)[2] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM3);
    (psda->ahptr)[3] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM4);
    (psda->ahptr)[4] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM5);
    (psda->ahptr)[5] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM4);
    (psda->ahptr)[6] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM3);
    (psda->ahptr)[7] = WinLoadPointer(HWND_DESKTOP, hmod, ID_ICONSDANIM2);
}

/*
 *@@ xsdFreeAnimation:
 *      this frees the animation loaded by xsdLoadAnimation.
 */

VOID xsdFreeAnimation(PSHUTDOWNANIM psda)
{
    USHORT us;
    for (us = 0; us < XSD_ANIM_COUNT; us++)
    {
        WinDestroyPointer((psda->ahptr)[us]);
        (psda->ahptr)[us] = NULLHANDLE;
    }
}

/*
 *@@ fnwpAutoCloseDetails:
 *      dlg func for "Auto-Close Details".
 *      This gets called from the notebook callbacks
 *      for the "XDesktop" notebook page
 *      (fncbDesktop1ItemChanged, xfdesk.c).
 */

MRESULT EXPENTRY fnwpAutoCloseDetails(HWND hwndDlg, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc = (MPARAM)NULL;

    switch (msg) {
        case WM_INITDLG: {
            ULONG       ulKeyLength;
            PSZ         p, pINI;

            // create window data in QWL_USER
            PAUTOCLOSEWINDATA pData = malloc(sizeof(AUTOCLOSEWINDATA));
            memset(pData, 0, sizeof(AUTOCLOSEWINDATA));

            // set animation
            xsdLoadAnimation(&sdAnim);
            anmPrepareAnimation(WinWindowFromID(hwndDlg, ID_SDDI_ICON),
                            XSD_ANIM_COUNT,
                            &(sdAnim.ahptr[0]),
                            150,    // delay
                            TRUE);  // start now

            pData->usItemCount = 0;

            // get existing items from INI
            if (PrfQueryProfileSize(HINI_USER,
                        INIAPP_XFOLDER, INIKEY_AUTOCLOSE,
                        &ulKeyLength))
            {
                //printf("Size: %d\n", ulKeyLength);
                // items exist: evaluate
                pINI = malloc(ulKeyLength);
                if (pINI)
                {
                    PrfQueryProfileData(HINI_USER,
                                INIAPP_XFOLDER, INIKEY_AUTOCLOSE,
                                pINI,
                                &ulKeyLength);
                    p = pINI;
                    //printf("%s\n", p);
                    while (strlen(p)) {
                        PAUTOCLOSELISTITEM pliNew = malloc(sizeof(AUTOCLOSELISTITEM));
                        strcpy(pliNew->szItemName, p);
                        lstAppendItem((PLISTITEM*)&(pData->pliAutoClose), NULL,
                                (PLISTITEM)pliNew);

                        WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                        LM_INSERTITEM,
                                        (MPARAM)LIT_END,
                                        (MPARAM)p);
                        p += (strlen(p)+1);

                        if (strlen(p)) {
                            pliNew->usAction = *((PUSHORT)p);
                            p += sizeof(USHORT);
                        }
                        pData->usItemCount++;
                    }
                    free(pINI);
                }
            }

            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                EM_SETTEXTLIMIT, (MPARAM)(100-1), MPNULL);

            WinSetWindowULong(hwndDlg, QWL_USER, (ULONG)pData);

            WinPostMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
        break; }

        case WM_CONTROL: {
            switch (SHORT1FROMMP(mp1)) {

                case ID_XSDI_XRB_LISTBOX: {
                    if (SHORT2FROMMP(mp1) == LN_SELECT)
                        // listbox was clicked on: update other
                        // controls with new data
                        WinSendMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
                break; }

                case ID_XSDI_XRB_ITEMNAME: {
                    if (SHORT2FROMMP(mp1) == EN_KILLFOCUS) {
                        // user changed item title: update data
                        PAUTOCLOSEWINDATA pData =
                                (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                        //printf("WM_CONTROL ID_XSDI_XRB_ITEMNAME EN_KILLFOCUS\n");
                        if (pData) {
                            if (pData->pliSelected) {
                                WinQueryDlgItemText(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                                    sizeof(pData->pliSelected->szItemName)-1,
                                    pData->pliSelected->szItemName);
                                WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_SETITEMTEXT,
                                    (MPARAM)pData->sSelected,
                                    (MPARAM)(pData->pliSelected->szItemName));
                            }
                        }
                    }
                break; }

                // radio buttons
                case ID_XSDI_ACL_WMCLOSE:
                case ID_XSDI_ACL_CTRL_C:
                case ID_XSDI_ACL_KILLSESSION:
                case ID_XSDI_ACL_SKIP: {
                    if (SHORT2FROMMP(mp1) == BN_CLICKED) {
                        PAUTOCLOSEWINDATA pData =
                                (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                        //printf("WM_CONTROL ID_XSDI_XRB_ITEMNAME EN_KILLFOCUS\n");
                        if (pData) {
                            if (pData->pliSelected) {
                                pData->pliSelected->usAction =
                                    (SHORT1FROMMP(mp1) == ID_XSDI_ACL_WMCLOSE)
                                        ? ACL_WMCLOSE
                                    : (SHORT1FROMMP(mp1) == ID_XSDI_ACL_CTRL_C)
                                        ? ACL_CTRL_C
                                    : (SHORT1FROMMP(mp1) == ID_XSDI_ACL_KILLSESSION)
                                        ? ACL_KILLSESSION
                                    : ACL_SKIP;
                            }
                        }
                    }
                break; }

                default:
                    mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
            }
        break; }

        case WM_UPDATE: {
            // posted from various locations to wholly update
            // the dlg items
            PAUTOCLOSEWINDATA pData =
                    (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
            //printf("WM_CONTROL ID_XSDI_XRB_LISTBOX LN_SELECT\n");
            if (pData) {
                pData->pliSelected = NULL;
                pData->sSelected = (USHORT)WinSendDlgItemMsg(hwndDlg,
                        ID_XSDI_XRB_LISTBOX,
                        LM_QUERYSELECTION,
                        (MPARAM)LIT_CURSOR,
                        MPNULL);
                //printf("  Selected: %d\n", pData->sSelected);
                if (pData->sSelected != LIT_NONE) {
                    pData->pliSelected = (PAUTOCLOSELISTITEM)lstItemFromIndex(
                            (PLISTITEM)(pData->pliAutoClose),
                            pData->sSelected);
                }

                if (pData->pliSelected) {
                    WinSetDlgItemText(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            pData->pliSelected->szItemName);
                    switch (pData->pliSelected->usAction) {
                        case ACL_WMCLOSE:
                            winhSetDlgItemChecked(hwndDlg,
                                ID_XSDI_ACL_WMCLOSE, 1); break;
                        case ACL_CTRL_C:
                            winhSetDlgItemChecked(hwndDlg,
                                ID_XSDI_ACL_CTRL_C, 1); break;
                        case ACL_KILLSESSION:
                            winhSetDlgItemChecked(hwndDlg,
                                ID_XSDI_ACL_KILLSESSION, 1); break;
                        case ACL_SKIP:
                            winhSetDlgItemChecked(hwndDlg,
                                ID_XSDI_ACL_SKIP, 1); break;
                    }
                } else {
                    WinSetDlgItemText(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            "");
                }
                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            (pData->pliSelected != NULL));

                winhEnableDlgItem(hwndDlg, ID_XSDI_ACL_WMCLOSE,
                            (pData->pliSelected != NULL));
                winhEnableDlgItem(hwndDlg, ID_XSDI_ACL_CTRL_C,
                            (pData->pliSelected != NULL));
                winhEnableDlgItem(hwndDlg, ID_XSDI_ACL_KILLSESSION,
                            (pData->pliSelected != NULL));
                winhEnableDlgItem(hwndDlg, ID_XSDI_ACL_SKIP,
                            (pData->pliSelected != NULL));

                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_DELETE,
                            (   (pData->usItemCount > 0)
                             && (pData->pliSelected)
                            )
                        );
            }
        break; }

        case WM_COMMAND: {
            switch (SHORT1FROMMP(mp1)) {

                /*
                 * ID_XSDI_XRB_NEW:
                 *      create new item
                 */

                case ID_XSDI_XRB_NEW: {
                    PAUTOCLOSEWINDATA pData =
                            (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    PAUTOCLOSELISTITEM pliNew = malloc(sizeof(AUTOCLOSELISTITEM));
                    //printf("WM_COMMAND ID_XSDI_XRB_NEW BN_CLICKED\n");
                    strcpy(pliNew->szItemName, "???");
                    pliNew->usAction = ACL_SKIP;
                    lstAppendItem((PLISTITEM*)&(pData->pliAutoClose), NULL,
                            (PLISTITEM)pliNew);

                    pData->usItemCount++;
                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_INSERTITEM,
                                    (MPARAM)LIT_END,
                                    (MPARAM)pliNew->szItemName);
                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_SELECTITEM, // will cause WM_UPDATE
                                    (MPARAM)(lstCountItems(
                                            (PLISTITEM)(pData->pliAutoClose))),
                                    (MPARAM)TRUE);
                    winhSetDlgItemFocus(hwndDlg, ID_XSDI_XRB_ITEMNAME);
                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            EM_SETSEL,
                            MPFROM2SHORT(0, 1000), // select all
                            MPNULL);
                break; }

                /*
                 * ID_XSDI_XRB_DELETE:
                 *      delete selected item
                 */

                case ID_XSDI_XRB_DELETE: {
                    PAUTOCLOSEWINDATA pData =
                            (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    //printf("WM_COMMAND ID_XSDI_XRB_DELETE BN_CLICKED\n");
                    if (pData) {
                        if (pData->pliSelected) {
                            lstRemoveItem((PLISTITEM*)&(pData->pliAutoClose), NULL,
                                    (PLISTITEM)pData->pliSelected);
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_DELETEITEM,
                                    (MPARAM)pData->sSelected,
                                    MPNULL);
                        }
                        WinPostMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
                    }
                    winhSetDlgItemFocus(hwndDlg, ID_XSDI_XRB_LISTBOX);
                break; }

                /*
                 * DID_OK:
                 *      store data in INI and dismiss dlg
                 */

                case DID_OK: {
                    PAUTOCLOSEWINDATA pData =
                            (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    PSZ     pINI, p;
                    BOOL    fValid = TRUE;

                    // store data in INI
                    if (pData->pliAutoClose) {
                        pINI = malloc(
                                    sizeof(AUTOCLOSELISTITEM)
                                  * lstCountItems((PLISTITEM)(pData->pliAutoClose))
                               );
                        memset(pINI, 0,
                                    sizeof(AUTOCLOSELISTITEM)
                                  * lstCountItems((PLISTITEM)(pData->pliAutoClose)));

                        if (pINI) {
                            PAUTOCLOSELISTITEM pli = pData->pliAutoClose;
                            USHORT          usCurrent = 0;
                            p = pINI;
                            while (pli) {
                                if (    (strlen(pli->szItemName) == 0)
                                   )
                                {
                                    WinAlarm(HWND_DESKTOP, WA_ERROR);
                                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                                    LM_SELECTITEM,
                                                    (MPARAM)usCurrent,
                                                    (MPARAM)TRUE);
                                    fValid = FALSE;
                                    break;
                                }
                                strcpy(p, pli->szItemName);
                                p += (strlen(p)+1);
                                *((PUSHORT)p) = pli->usAction;
                                p += sizeof(USHORT);

                                pli = pli->pNext;
                                usCurrent++;
                            }

                            PrfWriteProfileData(HINI_USER,
                                        INIAPP_XFOLDER, INIKEY_AUTOCLOSE,
                                        pINI,
                                        (p - pINI + 2));

                            free (pINI);
                        }
                    } // end if (pData->pliAutoClose)
                    else
                        PrfWriteProfileData(HINI_USER,
                                    INIAPP_XFOLDER, INIKEY_AUTOCLOSE,
                                    NULL, 0);

                    // dismiss dlg
                    if (fValid)
                        mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
                break; }

                default: { // includes DID_CANCEL
                    mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
                break; }
            }
        break; }

        /*
         * WM_DESTROY:
         *      clean up allocated memory
         */

        case WM_DESTROY: {
            PAUTOCLOSEWINDATA pData = (PAUTOCLOSEWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
            anmStopAnimation(WinWindowFromID(hwndDlg, ID_SDDI_ICON));
            xsdFreeAnimation(&sdAnim);
            lstClear((PLISTITEM*)&(pData->pliAutoClose), NULL);
            mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
        break; } // continue

        default:
            mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
        break;
    }
    return (mrc);
}

/*
 *@@ fnwpRebootExt:
 *      dlg proc for the "Extended Reboot" options.
 *      This gets called from the notebook callbacks
 *      for the "XDesktop" notebook page
 *      (fncbDesktop1ItemChanged, xfdesk.c).
 */

MRESULT EXPENTRY fnwpRebootExt(HWND hwndDlg, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc = (MPARAM)NULL;

    switch (msg) {
        case WM_INITDLG: {
            ULONG       ulKeyLength;
            PSZ         p, pINI;

            // create window data in QWL_USER
            PREBOOTWINDATA pData = malloc(sizeof(REBOOTWINDATA));
            memset(pData, 0, sizeof(REBOOTWINDATA));

            // set animation
            xsdLoadAnimation(&sdAnim);
            anmPrepareAnimation(WinWindowFromID(hwndDlg, ID_SDDI_ICON),
                            XSD_ANIM_COUNT,
                            &(sdAnim.ahptr[0]),
                            150,    // delay
                            TRUE);  // start now

            pData->usItemCount = 0;

            // get existing items from INI
            if (PrfQueryProfileSize(HINI_USER,
                        INIAPP_XFOLDER, INIKEY_BOOTMGR,
                        &ulKeyLength))
            {
                // _Pmpf(( "Size: %d", ulKeyLength ));
                // items exist: evaluate
                pINI = malloc(ulKeyLength);
                if (pINI)
                {
                    PrfQueryProfileData(HINI_USER,
                                INIAPP_XFOLDER, INIKEY_BOOTMGR,
                                pINI,
                                &ulKeyLength);
                    p = pINI;
                    // _Pmpf(( "%s", p ));
                    while (strlen(p)) {
                        PREBOOTLISTITEM pliNew = malloc(sizeof(REBOOTLISTITEM));
                        strcpy(pliNew->szItemName, p);
                        lstAppendItem((PLISTITEM*)&(pData->pliReboot), NULL,
                                (PLISTITEM)pliNew);

                        WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                        LM_INSERTITEM,
                                        (MPARAM)LIT_END,
                                        (MPARAM)p);
                        p += (strlen(p)+1);

                        if (strlen(p)) {
                            strcpy(pliNew->szCommand, p);
                            p += (strlen(p)+1);
                        }
                        pData->usItemCount++;
                    }
                    free(pINI);
                }
            }

            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                EM_SETTEXTLIMIT, (MPARAM)(100-1), MPNULL);
            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_COMMAND,
                EM_SETTEXTLIMIT, (MPARAM)(CCHMAXPATH-1), MPNULL);

            WinSetWindowULong(hwndDlg, QWL_USER, (ULONG)pData);

            WinPostMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
        break; }

        case WM_CONTROL: {
            switch (SHORT1FROMMP(mp1)) {
                case ID_XSDI_XRB_LISTBOX: {
                    if (SHORT2FROMMP(mp1) == LN_SELECT)
                        WinSendMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
                break; }

                case ID_XSDI_XRB_ITEMNAME: {
                    if (SHORT2FROMMP(mp1) == EN_KILLFOCUS) {
                        PREBOOTWINDATA pData =
                                (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                        // _Pmpf(( "WM_CONTROL ID_XSDI_XRB_ITEMNAME EN_KILLFOCUS" ));
                        if (pData) {
                            if (pData->pliSelected) {
                                WinQueryDlgItemText(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                                    sizeof(pData->pliSelected->szItemName)-1,
                                    pData->pliSelected->szItemName);
                                WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_SETITEMTEXT,
                                    (MPARAM)pData->sSelected,
                                    (MPARAM)(pData->pliSelected->szItemName));
                            }
                        }
                    }
                break; }

                case ID_XSDI_XRB_COMMAND: {
                    if (SHORT2FROMMP(mp1) == EN_KILLFOCUS) {
                        PREBOOTWINDATA pData =
                                (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                        // _Pmpf(( "WM_CONTROL ID_XSDI_XRB_COMMAND EN_KILLFOCUS" ));
                        if (pData) {
                            if (pData->pliSelected)
                                WinQueryDlgItemText(hwndDlg, ID_XSDI_XRB_COMMAND,
                                    sizeof(pData->pliSelected->szCommand)-1,
                                    pData->pliSelected->szCommand);
                        }
                    }
                break; }

                default:
                    mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
            }
        break; }

        case WM_UPDATE: {
            PREBOOTWINDATA pData =
                    (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
            // _Pmpf(( "WM_CONTROL ID_XSDI_XRB_LISTBOX LN_SELECT" ));
            if (pData) {
                pData->pliSelected = NULL;
                pData->sSelected = (USHORT)WinSendDlgItemMsg(hwndDlg,
                        ID_XSDI_XRB_LISTBOX,
                        LM_QUERYSELECTION,
                        (MPARAM)LIT_CURSOR,
                        MPNULL);
                // _Pmpf(( "  Selected: %d", pData->sSelected ));
                if (pData->sSelected != LIT_NONE) {
                    pData->pliSelected = (PREBOOTLISTITEM)lstItemFromIndex(
                            (PLISTITEM)(pData->pliReboot),
                            pData->sSelected);
                }

                if (pData->pliSelected) {
                    WinSetDlgItemText(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            pData->pliSelected->szItemName);
                    WinSetDlgItemText(hwndDlg, ID_XSDI_XRB_COMMAND,
                            pData->pliSelected->szCommand);
                } else {
                    WinSetDlgItemText(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            "");
                    WinSetDlgItemText(hwndDlg, ID_XSDI_XRB_COMMAND,
                            "");
                }
                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            (pData->pliSelected != NULL));
                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_COMMAND ,
                            (pData->pliSelected != NULL));
                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_UP      ,
                            (   (pData->pliSelected != NULL)
                             && (pData->usItemCount > 1)
                             && (pData->sSelected > 0)
                            ));
                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_DOWN    ,
                            (   (pData->pliSelected != NULL)
                             && (pData->usItemCount > 1)
                             && (pData->sSelected < (pData->usItemCount-1))
                            ));

                winhEnableDlgItem(hwndDlg, ID_XSDI_XRB_DELETE,
                            (   (pData->usItemCount > 0)
                             && (pData->pliSelected)
                            )
                        );
            }
        break; }

        case WM_COMMAND: {
            switch (SHORT1FROMMP(mp1)) {

                /*
                 * ID_XSDI_XRB_NEW:
                 *      create new item
                 */

                case ID_XSDI_XRB_NEW: {
                    PREBOOTWINDATA pData =
                            (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    PREBOOTLISTITEM pliNew = malloc(sizeof(REBOOTLISTITEM));
                    // _Pmpf(( "WM_COMMAND ID_XSDI_XRB_NEW BN_CLICKED" ));
                    strcpy(pliNew->szItemName, "???");
                    strcpy(pliNew->szCommand, "???");
                    lstAppendItem((PLISTITEM*)&(pData->pliReboot), NULL,
                            (PLISTITEM)pliNew);

                    pData->usItemCount++;
                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_INSERTITEM,
                                    (MPARAM)LIT_END,
                                    (MPARAM)pliNew->szItemName);
                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_SELECTITEM,
                                    (MPARAM)(lstCountItems(
                                            (PLISTITEM)(pData->pliReboot))
                                         - 1),
                                    (MPARAM)TRUE);
                    winhSetDlgItemFocus(hwndDlg, ID_XSDI_XRB_ITEMNAME);
                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_ITEMNAME,
                            EM_SETSEL,
                            MPFROM2SHORT(0, 1000), // select all
                            MPNULL);
                break; }

                /*
                 * ID_XSDI_XRB_DELETE:
                 *      delete delected item
                 */

                case ID_XSDI_XRB_DELETE: {
                    PREBOOTWINDATA pData =
                            (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    // _Pmpf(( "WM_COMMAND ID_XSDI_XRB_DELETE BN_CLICKED" ));
                    if (pData) {
                        if (pData->pliSelected) {
                            lstRemoveItem((PLISTITEM*)&(pData->pliReboot), NULL,
                                    (PLISTITEM)pData->pliSelected);
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_DELETEITEM,
                                    (MPARAM)pData->sSelected,
                                    MPNULL);
                        }
                        WinPostMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
                    }
                    winhSetDlgItemFocus(hwndDlg, ID_XSDI_XRB_LISTBOX);
                break; }

                /*
                 * ID_XSDI_XRB_UP:
                 *      move selected item up
                 */

                case ID_XSDI_XRB_UP: {
                    PREBOOTWINDATA pData =
                            (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    if (pData) {
                        // _Pmpf(( "WM_COMMAND ID_XSDI_XRB_UP BN_CLICKED" ));
                        if (pData->pliSelected) {
                            PREBOOTLISTITEM pliNew = malloc(sizeof(REBOOTLISTITEM));
                            *pliNew = *(pData->pliSelected);
                            // remove selected
                            lstRemoveItem((PLISTITEM*)&(pData->pliReboot), NULL,
                                    (PLISTITEM)pData->pliSelected);
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_DELETEITEM,
                                    (MPARAM)pData->sSelected,
                                    MPNULL);
                            // insert item again
                            lstInsertItemAt((PLISTITEM*)&(pData->pliReboot), NULL,
                                    (PLISTITEM)pliNew,
                                    (pData->sSelected-1));
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                            LM_INSERTITEM,
                                            (MPARAM)(pData->sSelected-1),
                                            (MPARAM)pliNew->szItemName);
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                            LM_SELECTITEM,
                                            (MPARAM)(pData->sSelected-1),
                                            (MPARAM)TRUE); // select flag
                        }
                        WinPostMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
                    }
                    winhSetDlgItemFocus(hwndDlg, ID_XSDI_XRB_LISTBOX);
                break; }

                /*
                 * ID_XSDI_XRB_DOWN:
                 *      move selected item down
                 */

                case ID_XSDI_XRB_DOWN: {
                    PREBOOTWINDATA pData =
                            (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    if (pData) {
                        // _Pmpf(( "WM_COMMAND ID_XSDI_XRB_DOWN BN_CLICKED" ));
                        if (pData->pliSelected) {
                            PREBOOTLISTITEM pliNew = malloc(sizeof(REBOOTLISTITEM));
                            *pliNew = *(pData->pliSelected);
                            // remove selected
                            // _Pmpf(( "  Removing index %d", pData->sSelected ));
                            lstRemoveItem((PLISTITEM*)&(pData->pliReboot), NULL,
                                    (PLISTITEM)pData->pliSelected);
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                    LM_DELETEITEM,
                                    (MPARAM)pData->sSelected,
                                    MPNULL);
                            // insert item again
                            lstInsertItemAt((PLISTITEM*)&(pData->pliReboot), NULL,
                                    (PLISTITEM)pliNew,
                                    (pData->sSelected+1));
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                            LM_INSERTITEM,
                                            (MPARAM)(pData->sSelected+1),
                                            (MPARAM)pliNew->szItemName);
                            WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                            LM_SELECTITEM,
                                            (MPARAM)(pData->sSelected+1),
                                            (MPARAM)TRUE); // select flag
                        }
                        WinPostMsg(hwndDlg, WM_UPDATE, MPNULL, MPNULL);
                    }
                    winhSetDlgItemFocus(hwndDlg, ID_XSDI_XRB_LISTBOX);
                break; }

                /*
                 * DID_OK:
                 *      store data in INI and dismiss dlg
                 */

                case DID_OK: {
                    PREBOOTWINDATA pData =
                            (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
                    PSZ     pINI, p;
                    BOOL    fValid = TRUE;

                    // _Pmpf(( "WM_COMMAND DID_OK BN_CLICKED" ));
                    // store data in INI
                    if (pData->pliReboot) {
                        pINI = malloc(
                                    sizeof(REBOOTLISTITEM)
                                  * lstCountItems((PLISTITEM)(pData->pliReboot))
                               );
                        memset(pINI, 0,
                                    sizeof(REBOOTLISTITEM)
                                  * lstCountItems((PLISTITEM)(pData->pliReboot)));

                        if (pINI) {
                            PREBOOTLISTITEM pli = pData->pliReboot;
                            USHORT          usCurrent = 0;
                            p = pINI;
                            while (pli) {
                                if (    (strlen(pli->szItemName) == 0)
                                     || (strlen(pli->szCommand) == 0)
                                   )
                                {
                                    WinAlarm(HWND_DESKTOP, WA_ERROR);
                                    WinSendDlgItemMsg(hwndDlg, ID_XSDI_XRB_LISTBOX,
                                                    LM_SELECTITEM,
                                                    (MPARAM)usCurrent,
                                                    (MPARAM)TRUE);
                                    fValid = FALSE;
                                    break;
                                }
                                strcpy(p, pli->szItemName);
                                p += (strlen(p)+1);
                                strcpy(p, pli->szCommand);
                                p += (strlen(p)+1);

                                pli = pli->pNext;
                                usCurrent++;
                            }

                            PrfWriteProfileData(HINI_USER,
                                        INIAPP_XFOLDER, INIKEY_BOOTMGR,
                                        pINI,
                                        (p - pINI + 2));

                            free (pINI);
                        }
                    } // end if (pData->pliReboot)
                    else
                        PrfWriteProfileData(HINI_USER,
                                    INIAPP_XFOLDER, INIKEY_BOOTMGR,
                                    NULL, 0);

                    // dismiss dlg
                    if (fValid)
                        mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
                break; }

                default: { // includes DID_CANCEL
                    mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
                break; }
            }
        break; }

        /*
         * WM_DESTROY:
         *      clean up allocated memory
         */

        case WM_DESTROY: {
            PREBOOTWINDATA pData = (PREBOOTWINDATA)WinQueryWindowULong(hwndDlg, QWL_USER);
            // _Pmpf(( "WM_DESTROY" ));
            anmStopAnimation(WinWindowFromID(hwndDlg, ID_SDDI_ICON));
            xsdFreeAnimation(&sdAnim);
            lstClear((PLISTITEM*)&(pData->pliReboot), NULL);
            mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
        break; }

        default:
            mrc = fnwpDlgGeneric(hwndDlg, msg, mp1, mp2);
        break;
    }
    return (mrc);
}

/*
 *@@ xsdItemFromPID:
 *      searches a pliShutdownFirst for a process ID.
 */

PSHUTLISTITEM xsdItemFromPID(PSHUTLISTITEM pFirst, PID pid, HMTX hmtx, ULONG ulTimeout)
{
    PSHUTLISTITEM pItem = NULL;
    BOOL          fAccess = FALSE,
                  fSemOwned = FALSE;

    TRY_QUIET(excpt1)
    {
        if (hmtx) {
            fSemOwned = (DosRequestMutexSem(hmtx, ulTimeout) == NO_ERROR);
            fAccess = fSemOwned;
        } else
            fAccess = TRUE;

        if (fAccess)
        {
            if (pFirst) {
                pItem = pFirst;
                while (pItem)
                    if (pItem->swctl.idProcess == pid)
                        break;
                    else pItem = pItem->pNext;
            }
        }
    }
    CATCH(excpt1) { } END_CATCH;

    if (fSemOwned) {
        DosReleaseMutexSem(hmtx);
        fSemOwned = FALSE;
    }

    return (pItem);
}

/*
 *@@ xsdItemFromSID:
 *      searches a pliShutdownFirst for a session ID
 */

PSHUTLISTITEM xsdItemFromSID(PSHUTLISTITEM pFirst, ULONG sid, HMTX hmtx, ULONG ulTimeout)
{
    PSHUTLISTITEM pItem = NULL;
    BOOL          fAccess = FALSE,
                  fSemOwned = FALSE;

    TRY_QUIET(excpt1)
    {
        if (hmtx) {
            fSemOwned = (DosRequestMutexSem(hmtx, ulTimeout) == NO_ERROR);
            fAccess = fSemOwned;
        } else
            fAccess = TRUE;

        if (fAccess)
        {
            if (pFirst) {
                pItem = pFirst;
                while (pItem)
                    if (pItem->swctl.idSession == sid)
                        break;
                    else pItem = pItem->pNext;
            }
        }
    }
    CATCH(excpt1) { } END_CATCH;

    if (fSemOwned) {
        DosReleaseMutexSem(hmtx);
        fSemOwned = FALSE;
    }

    return (pItem);
}

/*
 *@@ xsdCountRemainingItems:
 *      counts the items left to be closed by counting
 *      the window list items and subtracting the items
 *      which were skipped
 */

ULONG xsdCountRemainingItems(VOID)
{
    ULONG   ulrc = 0;
    BOOL    fShutdownSemOwned = FALSE,
            fSkippedSemOwned = FALSE;

    TRY_QUIET(excpt1)
    {
        fShutdownSemOwned = (DosRequestMutexSem(hmtxShutdown, 4000) == NO_ERROR);
        fSkippedSemOwned = (DosRequestMutexSem(hmtxSkipped, 4000) == NO_ERROR);
        if ( (fShutdownSemOwned) && (fSkippedSemOwned) )
            ulrc = (
                        lstCountItems((PLISTITEM)pliShutdownFirst)
                      - lstCountItems((PLISTITEM)pliSkipped)
                   );
    }
    CATCH(excpt1) { } END_CATCH;

    if (fShutdownSemOwned) {
        DosReleaseMutexSem(hmtxShutdown);
        fShutdownSemOwned = FALSE;
    }
    if (fSkippedSemOwned) {
        DosReleaseMutexSem(hmtxSkipped);
        fSkippedSemOwned = FALSE;
    }

    return (ulrc);
}

/*
 *@@ xsdLongTitle:
 *      creates a descriptive string in pszTitle from pItem
 *      (for the main (debug) window listbox).
 */

void xsdLongTitle(PSZ pszTitle, PSHUTLISTITEM pItem)
{
    sprintf(pszTitle, "%s%s",
            pItem->swctl.szSwtitle,
            (pItem->swctl.uchVisibility == SWL_VISIBLE)
                ? (", visible")
                : ("")
        );

    strcat(pszTitle, ", ");
    switch (pItem->swctl.bProgType) {
        case PROG_DEFAULT: strcat(pszTitle, "default"); break;
        case PROG_FULLSCREEN: strcat(pszTitle, "OS/2 FS"); break;
        case PROG_WINDOWABLEVIO: strcat(pszTitle, "OS/2 win"); break;
        case PROG_PM:
            strcat(pszTitle, "PM, class: ");
            strcat(pszTitle, pItem->szClass);
        break;
        case PROG_VDM: strcat(pszTitle, "VDM"); break;
        case PROG_WINDOWEDVDM: strcat(pszTitle, "VDM win"); break;
        default: {
            sprintf(pszTitle+strlen(pszTitle), "? (%lX)");
        break;}
    }
    sprintf(pszTitle+strlen(pszTitle),
            ", hwnd: 0x%lX, pid: 0x%lX, sid: 0x%lX, pObj: 0x%lX",
            (ULONG)pItem->swctl.hwnd,
            (ULONG)pItem->swctl.idProcess,
            (ULONG)pItem->swctl.idSession,
            (ULONG)pItem->pObject
        );
}

/*
 *@@ xsdQueryCurrentItem:
 *      returns the next PSHUTLISTITEM to be
 *      closed (skipping the items that were
 *      marked to be skipped).
 */

PSHUTLISTITEM xsdQueryCurrentItem(VOID)
{
    PSHUTLISTITEM   pliShutItem = NULL, pliSkipItem = NULL;
    CHAR            szShutItem[1000],
                    szSkipItem[1000];
    BOOL    fShutdownSemOwned = FALSE,
            fSkippedSemOwned = FALSE;

    TRY_QUIET(excpt1)
    {
        fShutdownSemOwned = (DosRequestMutexSem(hmtxShutdown, 4000) == NO_ERROR);
        fSkippedSemOwned = (DosRequestMutexSem(hmtxSkipped, 4000) == NO_ERROR);

        if ((fShutdownSemOwned) && (fSkippedSemOwned))
        {
            pliShutItem = pliShutdownFirst;
            while (pliShutItem) {
                pliSkipItem = pliSkipped;
                while (pliSkipItem) {
                    xsdLongTitle(szShutItem, pliShutItem);
                    xsdLongTitle(szSkipItem, pliSkipItem);
                    if (strcmp(szShutItem, szSkipItem) == 0)
                        /* current shut item is on skip list:
                           break (==> take next shut item */
                        break;
                    else
                        pliSkipItem = pliSkipItem->pNext;
                }
                if (pliSkipItem != NULL)
                    // current item was skipped: take next one
                    pliShutItem = pliShutItem->pNext;
                else
                    /* current item is not on the skip list:
                       return this item */
                    break;
            }
        }
    }
    CATCH(excpt1) { } END_CATCH;

    if (fShutdownSemOwned) {
        DosReleaseMutexSem(hmtxShutdown);
        fShutdownSemOwned = FALSE;
    }
    if (fSkippedSemOwned) {
        DosReleaseMutexSem(hmtxSkipped);
        fSkippedSemOwned = FALSE;
    }

    return (pliShutItem);
}

/*
 *@@ xsdAppendShutListItem:
 *      this appends a new PSHUTLISTITEM to the given list
 *      and returns the address of the new item; the list
 *      to append to must be specified in *ppFirst / *ppLast.
 *
 *      NOTE: It is entirely the job of the caller to serialize
 *      access to the list, using mutex semaphores.
 *      The item to add is to be specified by swctl and possibly
 *      *pObject (if swctl describes an open WPS object).
 */

PSHUTLISTITEM xsdAppendShutListItem(
                    PSHUTLISTITEM *ppFirst, PSHUTLISTITEM *ppLast,
                                       // in/out: linked list to work on
                    SWCNTRL swctl,     // in: tasklist entry to add
                    WPObject *pObject) // in: !=NULL: WPS object
{
    PSHUTLISTITEM  pNewItem = NULL;

    pNewItem = malloc(sizeof(SHUTLISTITEM));

    if (pNewItem)
    {
        pNewItem->pObject = pObject;
        pNewItem->swctl = swctl;

        strcpy(pNewItem->szClass, "unknown");

        if (pObject)
            // for WPS objects, store additional data
            if (xwpsCheckObject(pObject)) {
                strncpy(pNewItem->szClass, (PSZ)_somGetClassName(pObject), sizeof(pNewItem->szClass)-1);

                pNewItem->swctl.szSwtitle[0] = '\0';
                strncpy(pNewItem->swctl.szSwtitle, _wpQueryTitle(pObject), sizeof(pNewItem->swctl.szSwtitle)-1);

                // always set PID and SID to that of the WPS,
                // because the tasklist returns garbage for
                // WPS objects
                pNewItem->swctl.idProcess = pidWPS;
                pNewItem->swctl.idSession = sidWPS;

                // set HWND to object-in-use-list data,
                // because the tasklist also returns garbage
                // for that
                if (_wpFindUseItem(pObject, USAGE_OPENVIEW, NULL)) {
                    pNewItem->swctl.hwnd = (_wpFindViewItem(pObject, VIEW_ANY, NULL))->handle;
                }
            }
            else
                // invalid object:
                pNewItem->pObject = NULL;
        else {
            // no WPS object: get window class name
            WinQueryClassName(swctl.hwnd,
                sizeof(pNewItem->szClass)-1,
                pNewItem->szClass);
        }

        // append to list
        lstAppendItem((PLISTITEM*)ppFirst, (PLISTITEM*)ppLast,
                (PLISTITEM)pNewItem);
    }

    return (pNewItem);
}

/*
 *@@ xsdBuildShutList:
 *      this routine builds a new ShutList by evaluating the
 *      system task list; this list is built in *ppFirst / *ppLast.
 *      NOTE: It is entirely the job of the caller to serialize
 *      access to the list, using mutex semaphores.
 *      We call xsdAppendShutListItem for each task list entry,
 *      if that entry is to be closed, so we're doing a few checks.
 */

PSHUTLISTITEM xsdBuildShutList(PSHUTLISTITEM *ppFirst, PSHUTLISTITEM *ppLast)
{
    PSWBLOCK        pSwBlock   = NULL;         // Pointer to information returned
    ULONG           ul,
                    cbItems    = 0,            // Number of items in list
                    ulBufSize  = 0;            // Size of buffer for information

    HAB             habDesktop = WinQueryAnchorBlock(HWND_DESKTOP);
    CHAR            szSwUpperTitle[100];
    WPObject        *pObj;
    BOOL            Append;

    // get all the tasklist entries into a buffer
    cbItems = WinQuerySwitchList(NULLHANDLE, NULL, 0);
    ulBufSize = (cbItems * sizeof(SWENTRY)) + sizeof(HSWITCH);
    pSwBlock = (PSWBLOCK)malloc(ulBufSize);
    cbItems = WinQuerySwitchList(NULLHANDLE, pSwBlock, ulBufSize);

    // loop through all the tasklist entries
    for (ul = 0; ul < (pSwBlock->cswentry); ul++)
    {
        strcpy(szSwUpperTitle, pSwBlock->aswentry[ul].swctl.szSwtitle);
        WinUpper(habDesktop, 0, 0, szSwUpperTitle);

        if (pSwBlock->aswentry[ul].swctl.bProgType == PROG_DEFAULT) {
            // in this case, we need to find out what
            // type the program has ourselves
            ULONG ulType = 0;
            PRCPROCESS prcp;
            // default for errors
            pSwBlock->aswentry[ul].swctl.bProgType = PROG_WINDOWABLEVIO;
            if (prcQueryProcessInfo(pSwBlock->aswentry[ul].swctl.idProcess, &prcp))
                // according to bsedos.h, the PROG_* types are identical
                // to the SSF_TYPE_* types, so we can use the data from
                // DosQProcStat
                pSwBlock->aswentry[ul].swctl.bProgType = prcp.ulSessionType;
        }

        // now we check which windows we add to the shutdown list
        if (        // skip if PID == 0
                (pSwBlock->aswentry[ul].swctl.idProcess != 0)
                    // skip the Shutdown windows
             && (pSwBlock->aswentry[ul].swctl.hwnd != hwndMain)
             && (pSwBlock->aswentry[ul].swctl.hwnd != hwndVioDlg)
             && (pSwBlock->aswentry[ul].swctl.hwnd != hwndShutdownStatus)
                    // skip invisible tasklist entries; this
                    // includes a PMWORKPLACE cmd.exe
             && (pSwBlock->aswentry[ul].swctl.uchVisibility == SWL_VISIBLE)

             #ifdef DEBUG_SHUTDOWN
                    // if we're in debug mode, skip the PMPRINTF window
                    // because we want to see debug output
                 && (strcmp(szSwUpperTitle, "PMPRINTF - 2.56") != 0)
             #endif
           )
        {
            // add window:
            Append = TRUE;
            pObj = NULL;

            if (pSwBlock->aswentry[ul].swctl.bProgType == PROG_WINDOWEDVDM)
                // DOS/Win-OS/2 window: get real PID/SID, because
                // the tasklist contains false data
                WinQueryWindowProcess(pSwBlock->aswentry[ul].swctl.hwnd,
                    &(pSwBlock->aswentry[ul].swctl.idProcess),
                    &(pSwBlock->aswentry[ul].swctl.idSession));
            else if (pSwBlock->aswentry[ul].swctl.idProcess == pidWPS)
            {
                PTHREADGLOBALS pThreadGlobals = xthrQueryGlobals();

                // PID == Workplace Shell PID: get SOM pointer from hwnd
                pObj = _wpclsQueryObjectFromFrame(_WPDesktop,
                            pSwBlock->aswentry[ul].swctl.hwnd);
                if (!pObj) {
                    if (pSwBlock->aswentry[ul].swctl.hwnd == hwndActiveDesktop) {
                        pObj = pActiveDesktop;
                    }
                    else
                        Append = FALSE;
                }

                // check for special objects
                if (    (pObj == pActiveDesktop)
                     || (pObj == pThreadGlobals->pAwakeWarpCenter)
                   )
                    // neither store Desktop nor WarpCenter,
                    // because we will close these manually
                    // during shutdown; these two need special
                    // handling
                    Append = FALSE;
            }

            if (Append)
                // if we have (Restart WPS) && ~(close all windows), append
                // only open WPS objects and not all windows
                if ( (!(psdParams->optWPSCloseWindows)) && (pObj == NULL) )
                    Append = FALSE;

            if (Append)
                xsdAppendShutListItem(ppFirst, ppLast,
                    pSwBlock->aswentry[ul].swctl,
                    pObj);
        }
    }

    free(pSwBlock);
    return (*ppFirst);
}

/*
 *@@ xsdUpdateListBox:
 *      this routine builds a new PSHUTITEM list from the
 *      pointer to the pointer of the first item (*ppliShutdownFirst)
 *      by setting its value to xsdBuildShutList's return value;
 *      it also fills the listbox in the "main" window, which
 *      is only visible in Debug mode.
 *      But even if it's invisible, the listbox is used for closing
 *      windows. Ugly, but nobody can see it. ;-)
 *      If *ppliShutdownFirst is != NULL the old shutlist is cleared
 *      also.
 */

void xsdUpdateListBox(HWND hwndListbox)
{
    PSHUTLISTITEM   pItem;
    CHAR            szTitle[1024];

    BOOL            fSemOwned = FALSE;

    TRY_QUIET(excpt1)
    {
        fSemOwned = (DosRequestMutexSem(hmtxShutdown, 4000) == NO_ERROR);
        if (fSemOwned)
        {
            if (pliShutdownFirst)
                lstClear((PLISTITEM*)&pliShutdownFirst, (PLISTITEM*)&pliShutdownLast);

            xsdBuildShutList(&pliShutdownFirst, &pliShutdownLast);

            WinEnableWindowUpdate(hwndListbox, FALSE);
            WinSendMsg(hwndListbox, LM_DELETEALL, MPNULL, MPNULL);

            pItem = pliShutdownFirst;
            while (pItem) {
                xsdLongTitle(szTitle, pItem);
                WinInsertLboxItem(hwndListbox, 0, szTitle);
                pItem = pItem->pNext;
            }
            WinEnableWindowUpdate(hwndListbox, TRUE);
        }
    }
    CATCH(excpt1) { } END_CATCH;

    if (fSemOwned) {
        DosReleaseMutexSem(hmtxShutdown);
        fSemOwned = FALSE;
    }
}

/*
 *@@ fncbUpdateINIStatus:
 *      this is a callback func for prfhSaveINIs
 */

MRESULT EXPENTRY fncbUpdateINIStatus(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    if (hwnd)
        WinSendMsg(hwnd, msg, mp1, mp2);
    return ((MPARAM)TRUE);
}

/*
 *@@ fncbINIError:
 *      callback func for prfhSaveINIs (/helpers/winh.c)
 */

MRESULT EXPENTRY fncbSaveINIError(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    return ((MRESULT)cmnMessageBox(HWND_DESKTOP, "XShutdown: Error",
            (PSZ)mp1,     // error text
            (ULONG)mp2)   // MB_ABORTRETRYIGNORE or something
       );
}

/*
 *@@ xsdRestartWPS:
 *      terminated the WPS process, which will lead
 *      to a WPS restart.
 */

VOID xsdRestartWPS(VOID)
{
    ULONG ul;

    // wait a maximum of 2 seconds while there's still
    // a system sound playing
    for (ul = 0; ul < 20; ul++)
        if (xthrIsPlayingSystemSound())
            DosSleep(100);
        else
            break;

    // terminate the current process,
    // which is PMSHELL.EXE. We cannot use DosExit()
    // directly, because this might mess up the
    // C runtime library.
    exit(0);        // 0 == no error
}

/*
 * Global variables for shutdown thread:
 *
 */

HAB             habShutdownThread;
HMQ             hmqShutdownThread;
ULONG           *pulShutdownFunc2;
PNLSSTRINGS     pNLSStrings = NULL;
HMODULE         hmodResource = NULLHANDLE;
CHAR            szLog[1000] = "";   // for XSHUTDWN.LOG

// flags for whether we're currently owning semaphores
BOOL            fAwakeObjectsSemOwned = FALSE,
                fShutdownSemOwned = FALSE,
                fSkippedSemOwned = FALSE;

/*
 *@@ xsd_fntShutdownThread:
 *      this is the main shutdown thread which is created by
 *      xfInitiateShutdown / xfRestartWPS when shutdown is about
 *      to begin.
 *
 *      Parameters: this thread is created using thrCreate
 *      (/helpers/threads.c), so it is passed a pointer
 *      to a THREADINFO structure. In that structure you
 *      must set ulData to point to a SHUTDOWNPARAMS structure.
 *
 *      Note: if you're trying to understand what's going on here,
 *      I recommend rebuilding XFolder with DEBUG_SHUTDOWN
 *      #define'd (see common.h for that). This will allow you
 *      to switch XShutdown into "Debug" mode by holding down
 *      the "Shift" key while selecting "Shutdown" from the
 *      Desktop's context menu.
 *
 *      Shutdown / Restart WPS runs in the following phases:
 *
 *      1)  First, all necessary preparations are done, i.e. two
 *          windows are created (the status window with the progress
 *          bar and the "main" window, which is only visible in debug
 *          mode, but processes all the messages). These two windows
 *          daringly share the same msg proc (fnwpShutdown below),
 *          but receive different messages, so this shan't hurt.
 *
 *      2)  xsd_fntShutdownThread then remains in a standard PM message
 *          loop until shutdown is cancelled by the user or all
 *          windows have been closed.
 *          In both cases, fnwpShutdown posts a WM_QUIT then.
 *
 *          The order of msg processing in xsd_fntShutdownThread / fnwpShutdown
 *          is the following:
 *
 *          a)  Upon WM_INITDLG, fnwpShutdown will create the Update
 *              thread (xsd_fntUpdateThread below) and do nothing more.
 *              This Update thread is responsible for monitoring the
 *              task list; every time an item is closed (or even opened!),
 *              it will post a ID_SDMI_UPDATESHUTLIST command to fnwpShutdown,
 *              which will then start working again.
 *
 *          b)  ID_SDMI_UPDATESHUTLIST will update the list of currently
 *              open windows (which is not touched by any other thread)
 *              by calling xsdUpdateListBox.
 *              Unless we're in debug mode (where shutdown has to be
 *              started manually), the first time ID_SDMI_UPDATESHUTLIST
 *              is received, we will post ID_SDDI_BEGINSHUTDOWN (go to c)).
 *              Otherwise (subsequent calls), we post ID_SDMI_CLOSEITEM
 *              (go to e)).
 *
 *          c)  ID_SDDI_BEGINSHUTDOWN will begin processing the contents
 *              of the Shutdown folder. After this is done,
 *              ID_SDMI_BEGINCLOSINGITEMS is posted.
 *
 *          d)  ID_SDMI_BEGINCLOSINGITEMS will prepare closing all windows
 *              by setting flagClosingItems to TRUE and then post the
 *              first ID_SDMI_CLOSEITEM.
 *
 *          e)  ID_SDMI_CLOSEITEM will now undertake the necessary
 *              actions for closing the first / next item on the list
 *              of items to close, that is, post WM_CLOSE to the window
 *              or kill the process or whatever.
 *              If no more items are left to close, we post
 *              ID_SDMI_PREPARESAVEWPS (go to g)).
 *              Otherwise, after this, the Shutdown thread is idle.
 *
 *          f)  When the window has actually closed, the Update thread
 *              realizes this because the task list will have changed.
 *              The next ID_SDMI_UPDATESHUTLIST will be posted by the
 *              Update thread then. Go back to b).
 *
 *          g)  ID_SDMI_PREPARESAVEWPS will save the state of all currently
 *              awake WPS objects by using the list which was maintained
 *              by the Worker thread all the while during the whole WPS session.
 *
 *          i)  After this, ID_SDMI_FLUSHBUFFERS is posted, which
 *              will either restart the WPS (in Restart WPS mode) and
 *              remain idle, or post a WM_QUIT (in Shutdown mode), which
 *              will end the msg loop. In this case, fAllWindowsClosed
 *              is set to TRUE in order to allow continuation.
 *
 *      3)  Depending on whether fnwpShutdown set fAllWindowsClosed to
 *          TRUE, we will then actually shut down the system or exit
 *          this thread (= shutdown cancelled), and the user may continue work.
 *
 *          Shutting down the system is done by calling xsdFinishShutdown,
 *          which will differentiate what needs to be done depending on
 *          what the user wants (new with V0.84).
 *          We will then either reboot the machine or run in an endless
 *          loop, if no reboot was desired, or call the functions for an
 *          APM 1.2 power-off in apm.c (V0.82).
 *
 *          When shutdown was cancelled by pressing the respective button,
 *          the Update thread is killed, all shutdown windows are closed,
 *          and then this thread also terminates.
 */

void _Optlink xsd_fntShutdownThread(PVOID ulpti)
{
    PSZ         pszErrMsg = NULL;
    QMSG        qmsg;
    APIRET      arc;

    // get the THREADINFO structure;
    // ulData points to SHUTDOWNPARAMS
    PTHREADINFO           pti = (PTHREADINFO)ulpti;

    // set the global XFolder threads structure pointer
    PTHREADGLOBALS pThreadGlobals = xthrQueryGlobals();

    // set some global data for all the following
    pNLSStrings = cmnQueryNLSStrings();
    hmodResource = NLS_MODULE;

    pulShutdownFunc2 = &(pThreadGlobals->ulShutdownFunc2);

    TRY_LOUD(excpt1)   // install "loud" exception handler (except.h)
    {
        // get shutdown params (global variable)
        psdParams = (PSHUTDOWNPARAMS)pti->ulData;

        // else: no exception
        fAllWindowsClosed = FALSE;
        fShutdownBegun = FALSE;
        fClosingApps = FALSE;

        hwndVioDlg = NULLHANDLE;
        strcpy(szVioTitle, "");

        // prepare shutdown log file, if enabled
        if (psdParams->optLog)
            hfShutdownLog = doshOpenLogFile(XFOLDER_SHUTDOWNLOG);

        if (hfShutdownLog) {
            DATETIME DT;
            DosGetDateTime(&DT);
            sprintf(szLog, "\n\nXFolder shutdown log -- Date: %02d/%02d/%04d, Time: %02d:%02d:%02d\n",
                DT.month, DT.day, DT.year,
                DT.hours, DT.minutes, DT.seconds);
            strcat(szLog, "--------------------------------------------------------\n");
            sprintf(szLog+strlen(szLog), "\nXFolder version: %s\n", XFOLDER_VERSION);
            sprintf(szLog+strlen(szLog), "\nShutdown thread started, TID: 0x%lX\n",
                            thrQueryID((PTHREADINFO)ulpti)
                   );
            sprintf(szLog+strlen(szLog), "Settings: RestartWPS %s, Confirm %s, Reboot %s, WPSCloseWnds %s, CloseVIOs %s, APMPowerOff %s\n\n",
                    (psdParams->optRestartWPS) ? "ON" : "OFF",
                    (psdParams->optConfirm) ? "ON" : "OFF",
                    (psdParams->optReboot) ? "ON" : "OFF",
                    (psdParams->optWPSCloseWindows) ? "ON" : "OFF",
                    (psdParams->optAutoCloseVIO) ? "ON" : "OFF",
                    (psdParams->optAPMPowerOff) ? "ON" : "OFF");
            doshWriteToLogFile(hfShutdownLog, szLog);
        }

        // raise our own priority; we will
        // still use the REGULAR class, but
        // with the maximum delta, so we can
        // get above nasty (DOS?) sessions
        DosSetPriority(PRTYS_THREAD,
                       PRTYC_REGULAR,
                       PRTYD_MAXIMUM, // priority delta
                       0);

        // create an event semaphore which signals to the Update thread
        // that the Shutlist has been updated by fnwpShutdown
        DosCreateEventSem(
            NULL,         // unnamed
            &hevUpdated,
            0,            // unshared
            FALSE         // not posted
        );

        // mutex semaphores for linked lists
        if (hmtxShutdown == NULLHANDLE) {
            DosCreateMutexSem("\\sem32\\ShutdownList", &hmtxShutdown, 0, FALSE);  // unnamed, unowned
            DosCreateMutexSem("\\sem32\\SkippedList", &hmtxSkipped, 0, FALSE);  // unnamed, unowned
        }

        if (habShutdownThread = WinInitialize(0))
        {
            if (hmqShutdownThread = WinCreateMsgQueue(habShutdownThread, 0))
            {
                SWCNTRL     swctl;
                HSWITCH     hswitch;
                ULONG       ul;
                ULONG       ulKeyLength = 0;

                WinCancelShutdown(hmqShutdownThread, TRUE);

                // set the global Desktop info
                pActiveDesktop = _wpclsQueryActiveDesktop(_WPDesktop);
                hwndActiveDesktop = _wpclsQueryActiveDesktopHWND(_WPDesktop);

                // check for auto-close items in OS2.INI
                // and build pliAutoClose list accordingly
                if (PrfQueryProfileSize(HINI_USER,
                            INIAPP_XFOLDER, INIKEY_AUTOCLOSE,
                            &ulKeyLength))
                {
                    // items exist: evaluate
                    PSZ p;
                    PSZ pINI = malloc(ulKeyLength);

                    if (hfShutdownLog)
                        doshWriteToLogFile(hfShutdownLog, "Auto-close items found: \n");

                    if (pINI)
                    {
                        PrfQueryProfileData(HINI_USER,
                                    INIAPP_XFOLDER, INIKEY_AUTOCLOSE,
                                    pINI,
                                    &ulKeyLength);
                        p = pINI;
                        while (strlen(p)) {
                            // parse the items, which are object
                            // handles stored as strings, separated
                            // by spaces
                            PAUTOCLOSELISTITEM pliNew = malloc(sizeof(AUTOCLOSELISTITEM));
                            strcpy(pliNew->szItemName, p);
                            lstAppendItem((PLISTITEM*)&(pliAutoCloseFirst), NULL,
                                    (PLISTITEM)pliNew);

                            p += (strlen(p)+1);

                            if (strlen(p)) {
                                pliNew->usAction = *((PUSHORT)p);
                                p += sizeof(USHORT);
                            }

                            if (hfShutdownLog) {
                                sprintf(szLog, "  %s, usAction: %d\n",
                                    pliNew->szItemName, pliNew->usAction);
                                doshWriteToLogFile(hfShutdownLog, szLog);
                            }
                        }
                        free(pINI);
                    }
                }

                if (hfShutdownLog) {
                    doshWriteToLogFile(hfShutdownLog, "Creating windows...\n");
                }

                // setup main (debug) window
                hwndMain = WinLoadDlg(HWND_DESKTOP, NULLHANDLE,
                                fnwpShutdown,
                                hmodResource,
                                ID_SDD_MAIN,
                                NULL);
                WinSendMsg(hwndMain,
                           WM_SETICON,
                           (MPARAM)pThreadGlobals->hSDIcon,
                            NULL);

                // query process and session IDs for
                //      a) the Workplace process
                WinQueryWindowProcess(hwndMain, &pidWPS, NULL);
                //      b) the PM process
                WinQueryWindowProcess(HWND_DESKTOP, &pidPM, NULL);
                sidPM = 1;  // should always be this, I hope

                // add ourselves to the tasklist
                swctl.hwnd = hwndMain;                  // window handle
                swctl.hwndIcon = pThreadGlobals->hSDIcon; // icon handle
                swctl.hprog = NULLHANDLE;               // program handle
                swctl.idProcess = pidWPS;               // PID
                swctl.idSession = 0;                    // SID
                swctl.uchVisibility = SWL_VISIBLE;      // visibility
                swctl.fbJump = SWL_JUMPABLE;            // jump indicator
                WinQueryWindowText(hwndMain, sizeof(swctl.szSwtitle), (PSZ)&swctl.szSwtitle);
                swctl.bProgType = PROG_DEFAULT;         // program type

                hswitch = WinAddSwitchEntry(&swctl);
                WinQuerySwitchEntry(hswitch, &swctl);
                sidWPS = swctl.idSession;               // get the "real" WPS SID

                // setup status window (always visible)
                hwndShutdownStatus = WinLoadDlg(HWND_DESKTOP, NULLHANDLE,
                           fnwpShutdown,
                           hmodResource,
                           ID_SDD_STATUS,
                           NULL);

                // set an icon for the status window
                WinSendMsg(hwndShutdownStatus,
                           WM_SETICON,
                           (MPARAM)pThreadGlobals->hSDIcon,
                           NULL);

                // subclass the static rectangle control in the dialog to make
                // it a progress bar */
                pbarProgressBarFromStatic(
                        WinWindowFromID(hwndShutdownStatus, ID_SDDI_PROGRESSBAR),
                        PBA_ALIGNCENTER | PBA_BUTTONSTYLE);

                WinShowWindow(hwndShutdownStatus, TRUE);
                WinSetActiveWindow(HWND_DESKTOP, hwndShutdownStatus);

                // animate the traffic light
                xsdLoadAnimation(&sdAnim);
                anmPrepareAnimation(WinWindowFromID(hwndShutdownStatus, ID_SDDI_ICON),
                                XSD_ANIM_COUNT,
                                &(sdAnim.ahptr[0]),
                                150,    // delay
                                TRUE);  // start now

                // debug mode: show "main" window, which
                // is invisible otherwise
                if (psdParams->optDebug) {
                    winhCenterWindow(hwndMain);
                    WinShowWindow(hwndMain, TRUE);
                }

                if (hfShutdownLog) {
                    doshWriteToLogFile(hfShutdownLog, "Now entering message loop...\n");
                }

                /*
                 * Standard PM message loop:
                 *
                 */

                // now enter the common message loop for the main (debug) and
                // status windows (fnwpShutdown); this will keep running
                // until closing all windows is complete or cancelled, upon
                // which fnwpShutdown will post WM_QUIT
                while (WinGetMsg(habShutdownThread, &qmsg, NULLHANDLE, 0, 0))
                {
                    WinDispatchMsg(habShutdownThread, &qmsg);
                }

                if (hfShutdownLog) {
                    doshWriteToLogFile(hfShutdownLog, "Done with message loop. Cleaning up Update thread...\n");
                }

                /*
                 * Cleanup:
                 *
                 */

                // OK, shutdown is either cancelled or complete; clean up
                *pulShutdownFunc2 = 2000;
                // close "main" window, but keep the status window
                WinDestroyWindow(hwndMain);

                // get rid of the Update thread
                if (thrQueryID(pThreadGlobals->ptiUpdateThread))
                {
                    thrClose(pThreadGlobals->ptiUpdateThread);
                    thrFree(&(pThreadGlobals->ptiUpdateThread));
                }
                DosCloseEventSem(hevUpdated);
                *pulShutdownFunc2 = 2200;

                // clean up lists
                fShutdownSemOwned = (DosRequestMutexSem(hmtxShutdown, 4000) == NO_ERROR);
                if (fShutdownSemOwned) {
                    lstClear((PLISTITEM*)&pliShutdownFirst,
                                (PLISTITEM*)&pliShutdownLast);
                    DosReleaseMutexSem(hmtxShutdown);
                    fShutdownSemOwned = FALSE;
                }
                fSkippedSemOwned = (DosRequestMutexSem(hmtxSkipped, 4000) == NO_ERROR);
                if (fSkippedSemOwned) {
                    lstClear((PLISTITEM*)&pliSkipped, NULL);
                    DosReleaseMutexSem(hmtxSkipped);
                    fSkippedSemOwned = FALSE;
                }

                lstClear((PLISTITEM*)&pliAutoCloseFirst, NULL);

                if (hmtxShutdown != NULLHANDLE) {
                    *pulShutdownFunc2 = 2201;
                    arc = DosCloseMutexSem(hmtxShutdown);
                    if ( (arc) && (hfShutdownLog) ) {
                        DosBeep(100, 100);
                        sprintf(szLog, "  Error %d closing hmtxShutdown!",
                                        arc);
                    }
                    hmtxShutdown = NULLHANDLE;
                }
                if (hmtxSkipped != NULLHANDLE) {
                    *pulShutdownFunc2 = 2202;
                    arc = DosCloseMutexSem(hmtxSkipped);
                    if ( (arc) && (hfShutdownLog) ) {
                        DosBeep(100, 100);
                        sprintf(szLog, "  Error %d closing hmtxSkipped!",
                                        arc);
                    }
                    hmtxSkipped = NULLHANDLE;
                }

                if (hfShutdownLog) {
                    doshWriteToLogFile(hfShutdownLog, "Done cleaning up.\n");
                }

                /*
                 * Restart WPS or shutdown:
                 *
                 */

                if (fAllWindowsClosed)
                {
                    // (fAllWindowsClosed = TRUE) only if shutdown was
                    // not cancelled; this means that all windows have
                    // been successfully closed and we can actually shut
                    // down the system

                    if (psdParams->optRestartWPS) {
                        // here we will actually restart the WPS
                        if (hfShutdownLog) {
                            sprintf(szLog, "Preparing WPS restart...\n");
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }
                        anmStopAnimation(WinWindowFromID(hwndShutdownStatus, ID_SDDI_ICON));
                        WinDestroyWindow(hwndShutdownStatus);
                        xsdFreeAnimation(&sdAnim);

                        if (hfShutdownLog) {
                            sprintf(szLog, "Restarting WPS: Calling DosExit(), closing log.\n");
                            doshWriteToLogFile(hfShutdownLog, szLog);
                            DosClose(hfShutdownLog);
                            hfShutdownLog = NULLHANDLE;
                        }
                        xsdRestartWPS();
                    }

                    // *** no restart WPS:
                    // call the termination routine, which
                    // will do the rest

                    xsdFinishShutdown();

                } // end if (fAllWindowsClosed)

                // the following code is only reached when shutdown was cancelled;
                // clean up on the way out

                if (hfShutdownLog) {
                    sprintf(szLog, "Reached cleanup, closing log.\n");
                    doshWriteToLogFile(hfShutdownLog, szLog);
                    DosClose(hfShutdownLog);
                    hfShutdownLog = NULLHANDLE;
                }
            }
        }
    }
    CATCH(excpt1) {
        // exception occured:
        HWND hwndDesktop;

        *pulShutdownFunc2 = 515;

        // check for whether we're owning semaphores;
        // if we do, release them now
        if (fAwakeObjectsSemOwned) {
            DosReleaseMutexSem(pThreadGlobals->hmtxAwakeObjects);
            fAwakeObjectsSemOwned = FALSE;
        }
        if (fShutdownSemOwned) {
            DosReleaseMutexSem(hmtxShutdown);
            fShutdownSemOwned = FALSE;
        }
        if (fSkippedSemOwned) {
            DosReleaseMutexSem(hmtxSkipped);
            fSkippedSemOwned = FALSE;
        }

        hwndDesktop = _wpclsQueryActiveDesktopHWND(_WPDesktop);
        if (    (hwndDesktop)
             && (pszErrMsg == NULL)
           )
        {
            // only report the first error, or otherwise we will
            // jam the system with msg boxes
            *pulShutdownFunc2 = 516;
            pszErrMsg = malloc(1000);
            if (pszErrMsg) {
                strcpy(pszErrMsg, "An error occured in the XFolder Shutdown thread. "
                        "In the root directory of your boot drive, you will find a "
                        "file named XFLDTRAP.LOG, which contains debugging information. "
                        "If you had shutdown logging enabled, you will also find the "
                        "file XSHUTDWN.LOG there. If not, please enable shutdown "
                        "logging in the Desktop's settings notebook. "
                        "\n\nThe XShutdown procedure will be terminated now. We can "
                        "now also restart the Workplace Shell. This is recommended if "
                        "your Desktop has already been closed or if "
                        "the error occured during the saving of the INI files. In these "
                        "cases, please disable XShutdown and perform a regular OS/2 "
                        "shutdown to prevent loss of your WPS data."
                        "\n\nRestart the Workplace Shell now?");
                xthrPostWorkplaceObjectMsg(XOM_EXCEPTIONCAUGHT, (MPARAM)pszErrMsg,
                        (MPARAM)1); // enforce WPS restart

                if (hfShutdownLog) {
                    doshWriteToLogFile(hfShutdownLog, "\n*** CRASH\n");
                    doshWriteToLogFile(hfShutdownLog, pszErrMsg);
                    doshWriteToLogFile(hfShutdownLog, "\n");
                }
            }
        }

        // get out of here
        *pulShutdownFunc2 = 515;
    } END_CATCH;

    // clean up on the way out
    *pulShutdownFunc2 = 2105;
    WinDestroyWindow(hwndShutdownStatus);
    WinDestroyMsgQueue(hmqShutdownThread);
    WinTerminate(habShutdownThread);

    // set the global flag for whether shutdown is
    // running to FALSE; this will re-enable the
    // items in the Desktop's context menu
    pThreadGlobals->fShutdownRunning = FALSE;

    // end of Shutdown thread: we need to set our handle to 0
    //   because otherwise xfInitiateShutdown would prohibit a
    //   second shutdown
    *pulShutdownFunc2 = 2301;
    thrGoodbye((PTHREADINFO)ulpti);

    // thread exits!
}

/*
 *@@ fncbShutdown:
 *      callback function when processing Shutdown folder
 */

ULONG   hPOC;

MRESULT EXPENTRY fncbShutdown(HWND hwndStatus, ULONG ulObject, MPARAM mpNow, MPARAM mpMax)
{
    CHAR szStarting2[200];
    HWND rc = 0;
    if (ulObject) {
        WinSetActiveWindow(HWND_DESKTOP, hwndStatus);
        sprintf(szStarting2,
            (cmnQueryNLSStrings())->pszStarting,
            _wpQueryTitle((WPObject*)ulObject));
        WinSetDlgItemText(hwndStatus, ID_SDDI_STATUS, szStarting2);

        rc = _wpViewObject((WPObject*)ulObject, 0, OPEN_DEFAULT, 0);

        if (hfShutdownLog) {
            CHAR szLog[1000];
            sprintf(szLog, "      ""%s""\n", szStarting2);
            doshWriteToLogFile(hfShutdownLog, szLog);
        }

        WinSetActiveWindow(HWND_DESKTOP, hwndStatus);

        WinSendMsg(WinWindowFromID(hwndStatus, ID_SDDI_PROGRESSBAR),
            WM_UPDATEPROGRESSBAR, mpNow, mpMax);
    }
    else {
        WinPostMsg(hwndStatus, WM_COMMAND, MPFROM2SHORT(ID_SDMI_BEGINCLOSINGITEMS, 0),
            MPNULL);
        hPOC = 0;
    }
    return ((MRESULT)rc);
}

/*
 *@@ xsdUpdateClosingStatus:
 *      set status wnd text to "Closing xxx"
 */

VOID xsdUpdateClosingStatus(PSZ pszProgTitle)
{
    CHAR szTitle[300];
    strcpy(szTitle,
        (cmnQueryNLSStrings())->pszSDClosing);
    strcat(szTitle, " \"");
    strcat(szTitle, pszProgTitle);
    strcat(szTitle, "\"...");
    WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
        szTitle);

    WinSetActiveWindow(HWND_DESKTOP, hwndShutdownStatus);
}

ULONG       ulAwakeNow, ulAwakeMax;

/*
 *@@ fnwpShutdown:
 *      window procedure for both the main (debug) window and
 *      the status window; it receives messages from the Update
 *      threas, so that it can update the windows' contents
 *      accordingly; it also controls this thread by suspending
 *      or killing it, if necessary, and setting semaphores.
 *      Note that the main (debug) window with the listbox is only
 *      visible in debug mode (signalled to xfInitiateShutdown).
 */

MRESULT EXPENTRY fnwpShutdown(HWND hwndFrame, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT         mrc = MRFALSE;
    PSHUTLISTITEM   pItem;
    HWND            hwndListbox = WinWindowFromID(hwndMain, ID_SDDI_LISTBOX);
    CHAR            szLog[2000] = "";
    PTHREADGLOBALS pThreadGlobals = xthrQueryGlobals();

    *pulShutdownFunc2 = 1;

    switch(msg)
    {
        case WM_INITDLG:
        case WM_CREATE: {

            *pulShutdownFunc2 = 2;
            if (hfShutdownLog) {
                CHAR szTemp[2000];
                WinQueryWindowText(hwndFrame, sizeof(szTemp), szTemp);
                sprintf(szLog, "  WM_INITDLG/WM_CREATE (%s, hwnd: 0x%lX)\n",
                        szTemp, hwndFrame);
                doshWriteToLogFile(hfShutdownLog, szLog);
            }

            if (hfShutdownLog) {
                sprintf(szLog, "    HAB: 0x%lX, HMQ: 0x%lX, pidWPS: 0x%lX, pidPM: 0x%lX\n",
                    habShutdownThread, hmqShutdownThread, pidWPS, pidPM);
                doshWriteToLogFile(hfShutdownLog, szLog);
            }

            ulMaxItemCount = 0;
            ulLastItemCount = -1;

            hPOC = 0;

            if (thrQueryID(pThreadGlobals->ptiUpdateThread) == NULLHANDLE) {
                // first call; this one's called twice!

                /* if (pAwakeWarpCenter) {
                    _wpSaveImmediate(pAwakeWarpCenter);
                } */

                thrCreate(&(pThreadGlobals->ptiUpdateThread),
                            xsd_fntUpdateThread,
                            NULLHANDLE);

                if (hfShutdownLog) {
                    sprintf(szLog, "    Update thread started, tid: 0x%lX\n",
                            thrQueryID(pThreadGlobals->ptiUpdateThread));
                    doshWriteToLogFile(hfShutdownLog, szLog);
                }
            }
        break; }

        case WM_COMMAND: {
            switch (SHORT1FROMMP(mp1)) {
                case ID_SDDI_BEGINSHUTDOWN: {
                    /* this is either posted by the "Begin shutdown"
                       button (in debug mode) or otherwise automatically
                       after the first update initiated by the Update
                       thread; we will look for a Shutdown folder first */

                    XFolder         *pShutdownFolder;

                    *pulShutdownFunc2 = 30;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDDI_BEGINSHUTDOWN, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    if (pShutdownFolder = _wpclsQueryFolder(_WPFolder, XFOLDER_SHUTDOWNID, TRUE))
                    {
                        if (hfShutdownLog) {
                            sprintf(szLog, "    Processing shutdown folder...\n");
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }

                        hPOC = _xfBeginProcessOrderedContent(pShutdownFolder, 0, // wait mode
                            &fncbShutdown, (ULONG)hwndShutdownStatus);

                        if (hfShutdownLog) {
                            sprintf(szLog, "    Done processing shutdown folder\n");
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }
                    }
                    else
                        goto beginclosingitems;
                break; }

                case ID_SDMI_BEGINCLOSINGITEMS:
                beginclosingitems: {
                    /* this is posted after processing of the shutdown
                       folder; shutdown actually begins now with closing
                       all windows */
                    *pulShutdownFunc2 = 40;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_BEGINCLOSINGITEMS, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }
                    fClosingApps = TRUE;
                    WinShowWindow(hwndShutdownStatus, TRUE);
                    winhEnableDlgItem(hwndMain, ID_SDDI_BEGINSHUTDOWN, FALSE);
                    WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_CLOSEITEM, 0),
                        MPNULL);
                break; }

                /*
                 * ID_SDMI_CLOSEITEM:
                 *     this msg is posted first upon receiving
                 *     ID_SDDI_BEGINSHUTDOWN and subsequently for every
                 *     window that is to be closed; we only INITIATE
                 *     closing the window here by posting messages
                 *     or killing the window; we then rely on the
                 *     update thread to realize that the window has
                 *     actually been removed from the Tasklist. The
                 *     Update thread then posts ID_SDMI_UPDATESHUTLIST,
                 *     which will then in turn post another ID_SDMI_CLOSEITEM
                 */

                case ID_SDMI_CLOSEITEM: {
                    *pulShutdownFunc2 = 50;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_CLOSEITEM, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    // get task list item to close from linked list
                    pItem = xsdQueryCurrentItem();
                    if (pItem) {
                        CHAR        szTitle[1024];
                        USHORT      usItem;

                        *pulShutdownFunc2 = 51;
                        // compose string from item
                        xsdLongTitle(szTitle, pItem);

                        if (hfShutdownLog) {
                            sprintf(szLog, "    Item: %s\n", szTitle);
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }

                        // find string in the invisible list box
                        usItem = (USHORT)WinSendMsg(hwndListbox,
                            LM_SEARCHSTRING,
                            MPFROM2SHORT(0, LIT_FIRST),
                            (MPARAM)szTitle);
                        // and select it
                        if ((usItem != (USHORT)LIT_NONE)) {
                            WinPostMsg(hwndListbox,
                                LM_SELECTITEM,
                                (MPARAM)usItem,
                                (MPARAM)TRUE);
                        }

                        // update status window: "Closing xxx"
                        xsdUpdateClosingStatus(pItem->swctl.szSwtitle);

                        // now check what kind of action needs to be done
                        if (pItem->pObject)
                        {   // we have a WPS window: use proper method
                            // to ensure that window data is saved
                            *pulShutdownFunc2 = 53;
                            _wpClose(pItem->pObject);
                            if (hfShutdownLog) {
                                doshWriteToLogFile(hfShutdownLog, "      Open WPS object, called wpClose(pObject)\n");
                            }
                        }
                        else if (pItem->swctl.hwnd) {
                            // no WPS window: differentiate further
                            *pulShutdownFunc2 = 55;
                            if (hfShutdownLog) {
                                sprintf(szLog, "      swctl.hwnd found: 0x%lX\n", pItem->swctl.hwnd);
                                doshWriteToLogFile(hfShutdownLog, szLog);
                            }

                            if (    (pItem->swctl.bProgType == PROG_VDM)
                                 || (pItem->swctl.bProgType == PROG_WINDOWEDVDM)
                                 || (pItem->swctl.bProgType == PROG_FULLSCREEN)
                                 || (pItem->swctl.bProgType == PROG_WINDOWABLEVIO)
                                 // || (pItem->swctl.bProgType == PROG_DEFAULT)
                               )
                            {
                                CHAR szTest[1000];

                                if (hfShutdownLog) {
                                    sprintf(szLog, "      Seems to be VIO, swctl.bProgType: 0x%lX\n", pItem->swctl.bProgType);
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }

                                xsdLongTitle(szTest, pItem);
                                if (    (hwndVioDlg == NULLHANDLE)
                                     || (strcmp(szTest, szVioTitle) != 0)
                                   )
                                {
                                    if (hfShutdownLog) {
                                        doshWriteToLogFile(hfShutdownLog, "      Posting ID_SDMI_CLOSEVIO\n");
                                    }
                                    // not a PM session: ask what to do (handled below)
                                    WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_CLOSEVIO, 0),
                                            MPNULL);
                                }
                            }
                            else if (WinWindowFromID(pItem->swctl.hwnd, FID_SYSMENU))
                            {   // window has system menu: close PM application
                                if (hfShutdownLog) {
                                    sprintf(szLog, "      Has system menu, posting WM_SAVEAPPLICATION to hwnd 0x%lX\n",
                                            pItem->swctl.hwnd);
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }
                                WinPostMsg(pItem->swctl.hwnd,
                                    WM_SAVEAPPLICATION,
                                    MPNULL, MPNULL);

                                if (hfShutdownLog) {
                                    sprintf(szLog, "      Posting WM_QUIT to hwnd 0x%lX\n",
                                            pItem->swctl.hwnd);
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }
                                WinPostMsg(pItem->swctl.hwnd,
                                    WM_QUIT,
                                    MPNULL, MPNULL);
                            }
                            else { // no system menu: try something more brutal
                                if (hfShutdownLog) {
                                    sprintf(szLog, "      Has no sys menu, posting WM_CLOSE to hwnd 0x%lX\n",
                                            pItem->swctl.hwnd);
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }
                                WinPostMsg(pItem->swctl.hwnd,
                                    WM_CLOSE,
                                    MPNULL,
                                    MPNULL);
                            }
                        }
                        else {
                            if (hfShutdownLog) {
                                sprintf(szLog, "      Helpless... leaving item alone, pid: 0x%lX\n", pItem->swctl.idProcess);
                                doshWriteToLogFile(hfShutdownLog, szLog);
                            }
                            // DosKillProcess(DKP_PROCESS, pItem->swctl.idProcess);
                        }
                    } // end if (pItem)
                    else  { // no more items left: enter phase 2 (save WPS)
                        *pulShutdownFunc2 = 56;
                        if (hfShutdownLog) {
                            doshWriteToLogFile(hfShutdownLog, "    All items closed. Posting ID_SDMI_PREPARESAVEWPS\n");
                        }
                        WinPostMsg(hwndMain, WM_COMMAND,
                            MPFROM2SHORT(ID_SDMI_PREPARESAVEWPS, 0),
                            MPNULL);    // first post
                    }
                break; }

                /*
                 * ID_SDDI_SKIPAPP:
                 *     comes from the "Skip" button
                 */

                case ID_SDDI_SKIPAPP: {
                    PSHUTLISTITEM   pSkipItem;
                    *pulShutdownFunc2 = 60;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDDI_SKIPAPP, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }
                    pItem = xsdQueryCurrentItem();
                    if (pItem) {
                        if (hfShutdownLog) {
                            sprintf(szLog, "    Adding %s to the list of skipped items\n",
                                pItem->swctl.szSwtitle);
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }
                        pSkipItem = malloc(sizeof(SHUTLISTITEM));
                        *pSkipItem = *pItem;

                        fSkippedSemOwned = (DosRequestMutexSem(hmtxSkipped, 4000) == NO_ERROR);
                        if (fSkippedSemOwned) {
                            lstAppendItem((PLISTITEM*)&pliSkipped, NULL,
                                        (PLISTITEM)pSkipItem);
                            DosReleaseMutexSem(hmtxSkipped);
                            fSkippedSemOwned = FALSE;
                        }
                    }
                    if (fClosingApps)
                        WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_UPDATEPROGRESSBAR, 0),
                            MPNULL);
                break; }

                /*
                 * ID_SDMI_CLOSEVIO:
                 *     this is posted by ID_SDMI_CLOSEITEM when
                 *     a non-PM session is encountered; we will now
                 *     either close this session automatically or
                 *     open up a confirmation dlg
                 */

                case ID_SDMI_CLOSEVIO: {
                    ULONG   ulReply;
                    CHAR    szText[500];
                    PAUTOCLOSELISTITEM pliAutoClose;

                    *pulShutdownFunc2 = 70;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_CLOSEVIO, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    pItem = xsdQueryCurrentItem();
                    if (pItem) {
                        xsdLongTitle(szVioTitle, pItem);
                        VioItem = *pItem;

                        if (hfShutdownLog) {
                            sprintf(szLog, "    VIO item: %s\n", szVioTitle);
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }

                        // activate VIO window
                        WinSetActiveWindow(HWND_DESKTOP, VioItem.swctl.hwnd);

                        // check if VIO window is on auto-close list
                        pliAutoClose = pliAutoCloseFirst;
                        while (pliAutoClose) {
                            if (strnicmp(VioItem.swctl.szSwtitle,
                                        pliAutoClose->szItemName,
                                        strlen(pliAutoClose->szItemName)) == 0)
                                break;
                            else
                                pliAutoClose = pliAutoClose->pNext;
                        }

                        if (pliAutoClose) {
                            if (hfShutdownLog) {
                                doshWriteToLogFile(hfShutdownLog, "    Found on auto-close list\n");
                            }
                            // item was found on auto-close list:
                            // perform user-configured action
                            switch (pliAutoClose->usAction) {
                                case ACL_WMCLOSE:
                                    WinPostMsg((WinWindowFromID(VioItem.swctl.hwnd, FID_SYSMENU)),
                                        WM_SYSCOMMAND,
                                        (MPARAM)SC_CLOSE, MPNULL);
                                    if (hfShutdownLog) {
                                        sprintf(szLog, "      Posted SC_CLOSE to sysmenu, hwnd: 0x%lX\n",
                                            WinWindowFromID(VioItem.swctl.hwnd, FID_SYSMENU));
                                        doshWriteToLogFile(hfShutdownLog, szLog);
                                    }
                                    break;

                                case ACL_CTRL_C:
                                    DosSendSignalException(VioItem.swctl.idProcess,
                                        XCPT_SIGNAL_INTR);
                                    if (hfShutdownLog) {
                                        sprintf(szLog, "      Sent INTR signal to pid 0x%lX\n",
                                            VioItem.swctl.idProcess);
                                        doshWriteToLogFile(hfShutdownLog, szLog);
                                    }
                                    break;

                                case ACL_KILLSESSION:
                                    DosKillProcess(DKP_PROCESS, VioItem.swctl.idProcess);
                                    if (hfShutdownLog) {
                                        sprintf(szLog, "      Killed pid 0x%lX\n",
                                            VioItem.swctl.idProcess);
                                        doshWriteToLogFile(hfShutdownLog, szLog);
                                    }
                                    break;

                                case ACL_SKIP:
                                    WinPostMsg(hwndMain, WM_COMMAND,
                                            MPFROM2SHORT(ID_SDDI_SKIPAPP, 0),
                                            MPNULL);
                                    if (hfShutdownLog) {
                                        doshWriteToLogFile(hfShutdownLog, "      Posted ID_SDDI_SKIPAPP\n");
                                    }
                                    break;
                            }
                        } else {
                            // not on auto-close list:
                            if (psdParams->optAutoCloseVIO) {
                                // auto-close enabled globally though:
                                if (hfShutdownLog) {
                                    sprintf(szLog, "    Not found on auto-close list, auto-close is on:\n");
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }
                                // "auto close VIOs" is on
                                if (VioItem.swctl.idSession == 1) {
                                    // for some reason, DOS/Windows sessions always
                                    // run in the Shell process, whose SID == 1
                                    WinPostMsg((WinWindowFromID(VioItem.swctl.hwnd, FID_SYSMENU)),
                                        WM_SYSCOMMAND,
                                        (MPARAM)SC_CLOSE, MPNULL);
                                    if (hfShutdownLog) {
                                        sprintf(szLog, "      Posted SC_CLOSE to hwnd 0x%lX\n",
                                            WinWindowFromID(VioItem.swctl.hwnd, FID_SYSMENU));
                                        doshWriteToLogFile(hfShutdownLog, szLog);
                                    }
                                } else {
                                    // OS/2 windows: kill
                                    DosKillProcess(DKP_PROCESS, VioItem.swctl.idProcess);
                                    if (hfShutdownLog) {
                                        sprintf(szLog, "      Killed pid 0x%lX\n",
                                            VioItem.swctl.idProcess);
                                        doshWriteToLogFile(hfShutdownLog, szLog);
                                    }
                                }
                            } else {
                                // no auto-close: confirmation wnd
                                if (hfShutdownLog) {
                                    sprintf(szLog, "    Not found on auto-close list, query-action dlg:\n");
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }
                                hwndVioDlg = WinLoadDlg(HWND_DESKTOP, hwndShutdownStatus,
                                            fnwpDlgGeneric,
                                            NLS_MODULE,
                                            ID_SDD_CLOSEVIO,
                                            NULL);
                                strcpy(szText, "\"");
                                strcat(szText, VioItem.swctl.szSwtitle);
                                WinQueryDlgItemText(hwndVioDlg, ID_SDDI_VDMAPPTEXT,
                                    100, &(szText[strlen(szText)]));
                                WinSetDlgItemText(hwndVioDlg, ID_SDDI_VDMAPPTEXT,
                                    szText);
                                winhCenterWindow(hwndVioDlg);
                                ulReply = WinProcessDlg(hwndVioDlg);
                                WinDestroyWindow(hwndVioDlg);
                                switch (ulReply) {
                                    case DID_OK:
                                        // "Close" button: kill process
                                        if (VioItem.swctl.idSession == 1) {
                                            // for some reason, DOS/Windows sessions always
                                            // run in the Shell process, whose SID == 1
                                            WinPostMsg((WinWindowFromID(VioItem.swctl.hwnd, FID_SYSMENU)),
                                                WM_SYSCOMMAND,
                                                (MPARAM)SC_CLOSE, MPNULL);
                                            if (hfShutdownLog) {
                                                sprintf(szLog, "      DID_OK = 'Close' pressed, posted SC_CLOSE to hwnd 0x%lX\n",
                                                    WinWindowFromID(VioItem.swctl.hwnd, FID_SYSMENU));
                                                doshWriteToLogFile(hfShutdownLog, szLog);
                                            }
                                        } else {
                                            // OS/2 windows: kill
                                            DosKillProcess(DKP_PROCESS, VioItem.swctl.idProcess);
                                            if (hfShutdownLog) {
                                                sprintf(szLog, "      DID_OK = 'Close' pressed, killed pid 0x%lX\n",
                                                    VioItem.swctl.idProcess);
                                                doshWriteToLogFile(hfShutdownLog, szLog);
                                            }
                                        }
                                        break;

                                    case ID_SDDI_CANCELSHUTDOWN:
                                        // "Cancel shutdown" button: pass to main window
                                        WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDDI_CANCELSHUTDOWN, 0),
                                            MPNULL);
                                        if (hfShutdownLog) {
                                            sprintf(szLog, "      'Cancel shutdown' pressed");
                                            doshWriteToLogFile(hfShutdownLog, szLog);
                                        }
                                        break;

                                    case ID_SDDI_SKIPAPP:
                                        WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDDI_SKIPAPP, 0),
                                            MPNULL);
                                        if (hfShutdownLog) {
                                            sprintf(szLog, "      'Skip' pressed");
                                            doshWriteToLogFile(hfShutdownLog, szLog);
                                        }
                                        break;
                                    /* if the VIO was closed manually, ID_SDMI_UPDATESHUTLIST will post
                                       a WM_CLOSE to the window, so that the return code will be DID_CANCEL;
                                       we need not handle this return code */
                                }

                                hwndVioDlg = NULLHANDLE;
                            } // end else (optAutoCloseVIO)
                        }
                    }

                    if (hfShutdownLog) {
                        sprintf(szLog, "  Done with ID_SDMI_CLOSEVIO\n");
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }
                break; }

                /*
                 * ID_SDMI_UPDATESHUTLIST:
                 *    this cmd comes from the Update thread when
                 *    the task list has changed. This happens when
                 *    1)    something was closed by this function
                 *    2)    the user has closed something
                 *    3)    and even if the user has OPENED something
                 *          new.
                 *    We will then rebuilt the pliShutdownFirst list and
                 *    continue closing items, if Shutdown is currently in progress
                 */

                case ID_SDMI_UPDATESHUTLIST: {
                    #ifdef DEBUG_SHUTDOWN
                        DosBeep(10000, 50);
                    #endif

                    *pulShutdownFunc2 = 80;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_UPDATESHUTLIST, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    xsdUpdateListBox(hwndListbox);
                        // this updates the Shutdown linked list
                    DosPostEventSem(hevUpdated);
                        // signal update to Update thread

                    if (hfShutdownLog) {
                        sprintf(szLog, "    Rebuilt shut list, %d items remaining\n",
                                xsdCountRemainingItems());
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    if (hwndVioDlg) {
                        USHORT usItem;
                        // "Close VIO" confirmation window is open:
                        // check if the item that was to be closed
                        // is still in the listbox; if so, exit,
                        // if not, close confirmation window and
                        // continue
                        usItem = (USHORT)WinSendMsg(hwndListbox,
                            LM_SEARCHSTRING,
                            MPFROM2SHORT(0, LIT_FIRST),
                            (MPARAM)&szVioTitle);

                        if ((usItem != (USHORT)LIT_NONE))
                            break;
                        else {
                            WinPostMsg(hwndVioDlg, WM_CLOSE, MPNULL, MPNULL);
                            /* this will result in a DID_CANCEL return code
                               for WinProcessDlg in ID_SDMI_CLOSEVIO above */
                            if (hfShutdownLog) {
                                sprintf(szLog, "    Closed VIO confirm' dlg\n");
                                doshWriteToLogFile(hfShutdownLog, szLog);
                            }
                        }
                    }
                    goto updateprogressbar;
                    // continue with update progress bar
                }

                /*
                 * ID_SDMI_UPDATEPROGRESSBAR:
                 *     well, update the progress bar in the
                 *     status window
                 */

                case ID_SDMI_UPDATEPROGRESSBAR:
                updateprogressbar: {
                    ULONG           ulItemCount, ulMax, ulNow;

                    *pulShutdownFunc2 = 90;
                    ulItemCount = xsdCountRemainingItems();
                    if (ulItemCount > ulMaxItemCount) {
                        ulMaxItemCount = ulItemCount;
                        ulLastItemCount = -1; // enforce update
                    }

                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_UPDATEPROGRESSBAR, hwnd: 0x%lX, remaining: %d, total: %d\n",
                                hwndFrame, ulItemCount, ulMaxItemCount);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    if ((ulItemCount) != ulLastItemCount) {
                        ulMax = ulMaxItemCount;
                        ulNow = (ulMax - ulItemCount);

                        WinSendMsg(WinWindowFromID(hwndShutdownStatus, ID_SDDI_PROGRESSBAR),
                            WM_UPDATEPROGRESSBAR,
                            (MPARAM)ulNow, (MPARAM)ulMax);
                        ulLastItemCount = (ulItemCount);
                    }

                    if (fShutdownBegun == FALSE)
                        if (!(psdParams->optDebug)) {
                            /* if this is the first time we're here,
                               begin shutdown, unless we're in debug mode */
                            WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDDI_BEGINSHUTDOWN, 0),
                                MPNULL);
                            fShutdownBegun = TRUE;
                            break;
                        }
                    if (fClosingApps)
                        /* if we're already in the process of shutting down, we will
                           initiate closing the next item */
                        WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_CLOSEITEM, 0),
                            MPNULL);
                break; }

                case DID_CANCEL:
                case ID_SDDI_CANCELSHUTDOWN:
                    /* results from the "Cancel shutdown" buttons in both the
                       main (debug) and the status window; we set a semaphore
                       upon which the Update thread will terminate itself,
                       and WM_QUIT is posted to the main (debug) window, so that
                       the message loop in the main Shutdown thread function ends */
                    if (winhIsDlgItemEnabled(hwndShutdownStatus, ID_SDDI_CANCELSHUTDOWN)) {
                        *pulShutdownFunc2 = 120;
                        if (hfShutdownLog) {
                            sprintf(szLog, "  DID_CANCEL/ID_SDDI_CANCELSHUTDOWN, hwnd: 0x%lX\n");
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }

                        if (hPOC)
                            ((PPROCESSCONTENTINFO)hPOC)->fCancelled = TRUE;
                        winhEnableDlgItem(hwndShutdownStatus, ID_SDDI_CANCELSHUTDOWN, FALSE);
                        winhEnableDlgItem(hwndShutdownStatus, ID_SDDI_SKIPAPP, FALSE);
                        winhEnableDlgItem(hwndMain, ID_SDDI_BEGINSHUTDOWN, TRUE);
                        fClosingApps = FALSE;

                        WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_CLEANUPANDQUIT, 0),
                            MPNULL);
                    }
                break;

            /* PHASE 2: after all windows have been closed,
               ID_SDMI_PREPARESAVEWPS is posted, so we will now
               call _wpSaveImmediate on all awake objects */

                /*
                 * ID_SDMI_PREPARESAVEWPS:
                 *      the first time, this will be posted with
                 *      mp2 == NULL, 2 or 3 afterwards
                 */

                case ID_SDMI_PREPARESAVEWPS: {
                    CHAR    szTitle[1000];
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_PREPARESAVEWPS, hwnd: 0x%lX, mp2: 0x%lX\n", hwndFrame, mp2);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    if (mp2 == (MPARAM)NULL)
                    {
                        // first post
                        WinSetActiveWindow(HWND_DESKTOP, hwndShutdownStatus);

                        winhEnableDlgItem(hwndShutdownStatus, ID_SDDI_CANCELSHUTDOWN, FALSE);
                        winhEnableDlgItem(hwndShutdownStatus, ID_SDDI_SKIPAPP, FALSE);

                        *pulShutdownFunc2 = 100;

                        // close Desktop window (which we excluded from
                        // the regular folders list)
                        xsdUpdateClosingStatus(_wpQueryTitle(pActiveDesktop));
                        if (hfShutdownLog) {
                            sprintf(szLog, "    Closing Desktop window\n");
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }
                        _wpSaveImmediate(pActiveDesktop);
                        _wpClose(pActiveDesktop);

                        // close WarpCenter next
                        if (pThreadGlobals->pAwakeWarpCenter) {
                            // WarpCenter open?
                            if (_wpFindUseItem(pThreadGlobals->pAwakeWarpCenter,
                                        USAGE_OPENVIEW,
                                        NULL)       // get first useitem
                                )
                            {
                                // if open: close it
                                xsdUpdateClosingStatus(_wpQueryTitle(pThreadGlobals->pAwakeWarpCenter));
                                if (hfShutdownLog) {
                                    sprintf(szLog, "    Closing WarpCenter\n");
                                    doshWriteToLogFile(hfShutdownLog, szLog);
                                }
                                _wpClose(pThreadGlobals->pAwakeWarpCenter);
                            }
                        }

                        WinPostMsg(hwndMain, WM_COMMAND,
                                MPFROM2SHORT(ID_SDMI_PREPARESAVEWPS, 0),
                                (MPARAM)2);    // second post: mp2 == 2
                        // we need to post this msg twice, because
                        // otherwise the status wnd doesn't get updated
                        // properly
                    }
                    else if ((ULONG)mp2 == 2)
                    {
                        // mp2 == 2: second post,
                        // now prepare saving objects

                        // request the mutex sem for access to the
                        // awake objects list; we do not release
                        // this semaphore until we're done saving
                        // the WPS objects, which will prevent the
                        // Worker thread from messing with that
                        // list during that time
                        fAwakeObjectsSemOwned = (DosRequestMutexSem(pThreadGlobals->hmtxAwakeObjects, 4000)
                                                    == NO_ERROR);
                        if (fAwakeObjectsSemOwned) {
                            ulAwakeMax = lstCountItems(
                                            (PLISTITEM)pThreadGlobals->pliAwakeObjectsFirst);
                        }
                        ulAwakeNow = 0;

                        sprintf(szTitle,
                                cmnQueryNLSStrings()->pszSDSavingDesktop,
                                    // "Saving xxx awake WPS objects..."
                                ulAwakeMax);
                        WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
                            szTitle);
                        if (hfShutdownLog) {
                            sprintf(szLog, "    Saving %d awake WPS objects...\n", ulAwakeMax);
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }

                        // now we need a blank screen so that it looks
                        // as if we had closed all windows, even if we
                        // haven't; we do this by creating a "fake
                        // desktop", which is just an empty window w/out
                        // title bar which takes up the whole screen and
                        // has the color of the PM desktop
                        if (    (!(psdParams->optRestartWPS))
                             && (!(psdParams->optDebug))
                           )
                            winhCreateFakeDesktop(hwndShutdownStatus);

                        WinPostMsg(hwndMain, WM_COMMAND,
                                MPFROM2SHORT(ID_SDMI_PREPARESAVEWPS, 0),
                                (MPARAM)3);    // third post: mp2 == 3
                    }
                    else if ((ULONG)mp2 == 3)
                    {
                        // third post:
                        // reset progress bar
                        WinSendMsg(WinWindowFromID(hwndShutdownStatus, ID_SDDI_PROGRESSBAR),
                                WM_UPDATEPROGRESSBAR,
                                (MPARAM)0, (MPARAM)ulAwakeMax);

                        // and wait a while
                        DosSleep(500);

                        if (fAwakeObjectsSemOwned) {
                            // finally, save WPS!!
                            WinPostMsg(hwndMain, WM_COMMAND,
                                    MPFROM2SHORT(ID_SDMI_SAVEWPSITEM, 0),
                                    // first item
                                    pThreadGlobals->pliAwakeObjectsFirst);
                        }
                    }
                break; }

                /*
                 * ID_SDMI_SAVEWPSITEM:
                 *      this is then posted for each awake WPS object,
                 *      with the current POBJECTLISTITEM in mp2.
                 *      For the first post (above), this contains
                 *      the first object in the linked list of awake
                 *      WPS objects maintained by the Worker thread;
                 *      we can then just go for the next object (*pNext)
                 *      and post this message again.
                 */

                case ID_SDMI_SAVEWPSITEM: {
                    POBJECTLISTITEM pliAwakeObject = (POBJECTLISTITEM)mp2;

                    /* now save all currently awake WPS objects; these will
                       be saved after the other windows are closed */

                    *pulShutdownFunc2 = 110;

                    if (pliAwakeObject)
                    {
                        // mp2 != NULL: save that object;
                        // we install an exception handler because
                        // this crashes sometimes (V0.84)
                        TRY_QUIET(excpt2) {
                            *pulShutdownFunc2 = 111;
                            if (pliAwakeObject->pObj) {
                                // save neither Desktop nor WarpCenter
                                if (    (pliAwakeObject->pObj != pActiveDesktop)
                                     && (pliAwakeObject->pObj != pThreadGlobals->pAwakeWarpCenter)
                                   )
                                    // save object data synchroneously
                                    _wpSaveImmediate(pliAwakeObject->pObj);
                            }
                            *pulShutdownFunc2 = 112;
                        }
                        CATCH(excpt2)
                        {
                            // ignore exceptions. These occur only very
                            // rarely when the data in the awake objects
                            // list is no longer valid because the Worker
                            // thread hasn't kept up (it has idle priority).
                            DosBeep(10000, 10);
                        } END_CATCH;

                        ulAwakeNow++;
                        // post for next object
                        WinPostMsg(hwndMain, WM_COMMAND,
                                MPFROM2SHORT(ID_SDMI_SAVEWPSITEM, 0),
                                pliAwakeObject->pNext);
                                    // this is NULL for the last object
                    }
                    else
                    {
                        // mp2 == NULL: no more objects in list,
                        // so we're done

                        // free the list mutex semaphore
                        if (fAwakeObjectsSemOwned) {
                            DosReleaseMutexSem(pThreadGlobals->hmtxAwakeObjects);
                            fAwakeObjectsSemOwned = FALSE;
                        }

                        *pulShutdownFunc2 = 115;
                        if (hfShutdownLog) {
                            doshWriteToLogFile(hfShutdownLog, "    Done saving WPS\n");
                        }
                        WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_FLUSHBUFFERS, 0),
                            MPNULL);
                        *pulShutdownFunc2 = 116;
                    }

                    *pulShutdownFunc2 = 118;
                    WinSendMsg(WinWindowFromID(hwndShutdownStatus, ID_SDDI_PROGRESSBAR),
                            WM_UPDATEPROGRESSBAR,
                            (MPARAM)ulAwakeNow, (MPARAM)ulAwakeMax);
                    *pulShutdownFunc2 = 119;
                break; }

             /* PHASE 3: after saving the WPS objects, ID_SDMI_FLUSHBUFFERS is
                posted, so we will now actually shut down the system or restart
                the WPS */

                case ID_SDMI_FLUSHBUFFERS: {
                    *pulShutdownFunc2 = 130;
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_FLUSHBUFFERS, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }

                    // close the Update thread to prevent it from interfering
                    // with what we're doing now
                    if (thrQueryID(pThreadGlobals->ptiUpdateThread)) {
                        #ifdef DEBUG_SHUTDOWN
                            WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
                                "Waiting for the Update thread to end...");
                        #endif
                        if (hfShutdownLog) {
                            sprintf(szLog, "    Closing Update thread, tid: 0x%lX...\n",
                                    thrQueryID(pThreadGlobals->ptiUpdateThread));
                            doshWriteToLogFile(hfShutdownLog, szLog);
                        }
                        thrFree(&(pThreadGlobals->ptiUpdateThread));  // close and wait
                        if (hfShutdownLog) {
                            doshWriteToLogFile(hfShutdownLog, "    Update thread terminated\n");
                        }
                    }

                    if (psdParams->optRestartWPS) {
                        // "Restart WPS" mode
                        ULONG i;

                        *pulShutdownFunc2 = 131;
                        WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
                                (cmnQueryNLSStrings())->pszSDRestartingWPS);
                        *pulShutdownFunc2 = 132;
                        if (psdParams->optWPSReuseStartupFolder)
                        {
                            // delete the "lastpid" file in \bin,
                            // which will cause the startup folder to be
                            // processed by the Worker thread after the
                            // WPS restart
                            CHAR szPIDFile[CCHMAXPATH];
                            cmnQueryXFolderBasePath(szPIDFile);
                            strcat(szPIDFile, "\\bin\\lastpid");
                            remove(szPIDFile);
                        }
                    }

                    // for both "Restart WPS" and "Shutdown" mode:
                    // exit fnwpShutdown and let the rest of the shutdown
                    // be handled by fntShutdown, which will take
                    // over after WM_QUIT

                    *pulShutdownFunc2 = 133;
                    fAllWindowsClosed = TRUE;
                    WinPostMsg(hwndMain, WM_COMMAND,
                            MPFROM2SHORT(ID_SDMI_CLEANUPANDQUIT, 0),
                            MPNULL);
                break; }

                /*
                 * ID_SDMI_CLEANUPANDQUIT:
                 *      for "real" shutdown: let xsd_fntShutdownThread handle the rest
                 */

                case ID_SDMI_CLEANUPANDQUIT: {
                    if (hfShutdownLog) {
                        sprintf(szLog, "  ID_SDMI_CLEANUPANDQUIT, hwnd: 0x%lX\n", hwndFrame);
                        doshWriteToLogFile(hfShutdownLog, szLog);
                    }
                    WinPostMsg(hwndMain, WM_QUIT, MPNULL, MPNULL);
                break; }

                default:
                    mrc = WinDefDlgProc(hwndFrame, msg, mp1, mp2);
            } // end switch;
        break; } // end case WM_COMMAND

        // other msgs: have them handled by the usual WC_FRAME wnd proc
        default:
           mrc = WinDefDlgProc(hwndFrame, msg, mp1, mp2);
        break;
    }

    *pulShutdownFunc2 = 199;

    return (mrc);
}

/* ******************************************************************
 *                                                                  *
 *   here comes the "Finish" routines                               *
 *                                                                  *
 ********************************************************************/


/*
 *  The following routines are called to finish shutdown
 *  after all windows have been closed and the system is
 *  to be shut down or APM power-off'ed or rebooted or
 *  whatever.
 */

HPS         hpsScreen = NULLHANDLE;

/*
 *@@ xsdFinishShutdown:
 *      this is the generic routine called by
 *      xsd_fntShutdownThread after closing all windows
 *      is complete and the system is to be shut
 *      down, depending on the user settings.
 *
 *      This evaluates the current shutdown settings and
 *      calls xsdFinishStandardReboot, xsdFinishUserReboot,
 *      xsdFinishAPMPowerOff, or xsdFinishStandardMessage.
 *
 *      Note that this is only called for "real" shutdown.
 *      For "Restart WPS", xsdRestartWPS is called instead.
 */

VOID xsdFinishShutdown(VOID)
{
    // change the mouse pointer to wait state
    HPOINTER    hptrOld = WinQueryPointer(HWND_DESKTOP);
    WinSetPointer(HWND_DESKTOP, WinQuerySysPointer(HWND_DESKTOP,
                            SPTR_WAIT, FALSE));

    if (psdParams->optAnimate)
        // hps for animation later
        hpsScreen = WinGetScreenPS(HWND_DESKTOP);

    // enforce saving of INIs
    *pulShutdownFunc2 = 2040;
    WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
            pNLSStrings->pszSDSavingProfiles);
    *pulShutdownFunc2 = 2050;

    if (hfShutdownLog)
        doshWriteToLogFile(hfShutdownLog, "Saving INIs...\n");

    *pulShutdownFunc2 = 2060;
    if (prfhSaveINIs(habShutdownThread, hfShutdownLog,
                (PFNWP)fncbUpdateINIStatus,
                    // callback function for updating the progress bar
                WinWindowFromID(hwndShutdownStatus, ID_SDDI_PROGRESSBAR),
                    // status window handle passed to callback
                WM_UPDATEPROGRESSBAR,
                    // message passed to callback
                (PFNWP)fncbSaveINIError,
                    // callback for errors
                &*pulShutdownFunc2)
                    // address where to store debug info
            != NO_ERROR)
        // error occured: ask whether to restart the WPS
        if (cmnMessageBoxMsg(hwndShutdownStatus, 110, 111, MB_YESNO)
                    == MBID_YES)
            xsdRestartWPS();

    *pulShutdownFunc2 = 2070;
    if (hfShutdownLog)
        doshWriteToLogFile(hfShutdownLog,
                "Done saving INIs; now preparing shutdown\n");

    // always say "releasing filesystems"
    WinShowPointer(HWND_DESKTOP, FALSE);
    *pulShutdownFunc2 = 2080;
    WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
            pNLSStrings->pszSDFlushing);

    // here comes the settings-dependent part:
    // depending on what we are to do after shutdown,
    // we switch to different routines which handle
    // the rest.
    // I guess this is a better solution than putting
    // all this stuff in the same routine because after
    // DosShutdown(), even the swapper file is blocked,
    // and if the following code is in the swapper file,
    // it cannot be executed. From user reports, I suspect
    // this has happened on some memory-constrained
    // systems with XFolder < V0.84. So we better put
    // all the code together which will be used together.

    if (psdParams->optReboot)
    {
        // reboot:
        if (strlen(psdParams->szRebootCommand) == 0)
            // no user reboot action:
            xsdFinishStandardReboot();
        else
            // user reboot action:
            xsdFinishUserReboot();
    }
    else if (psdParams->optAPMPowerOff)
    {
        // no reboot, but APM power off?
        xsdFinishAPMPowerOff();
    }
    else if (psdParams->optDebug)
    {
        // debug mode: just sleep a while
        DosSleep(2000);
    } else
        // normal shutdown: show message
        xsdFinishStandardMessage();

    // the xsdFinish* functions never return;
    // so we only get here in debug mode and
    // return
}

/*
 *@@ xsdFinishStandardMessage:
 *      this finishes the shutdown procedure,
 *      displaying the "Shutdown is complete..."
 *      window and halting the system.
 */

VOID xsdFinishStandardMessage(VOID)
{
    // setup Ctrl+Alt+Del message window; this needs to be done
    // before DosShutdown, because we won't be able to reach the
    // resource files after that
    HWND hwndCADMessage = WinLoadDlg(HWND_DESKTOP, NULLHANDLE,
                    WinDefDlgProc,
                    hmodResource,
                    ID_SDD_CAD,
                    NULL);
    winhCenterWindow(hwndCADMessage);  // wnd is still invisible

    if (hfShutdownLog) {
        sprintf(szLog, "xsdFinishStandardMessage: Calling DosShutdown(0), closing log.\n");
        doshWriteToLogFile(hfShutdownLog, szLog);
        DosClose(hfShutdownLog);
        hfShutdownLog = NULLHANDLE;
    }

    DosShutdown(0);

    if (psdParams->optAnimate)
        // cute power-off animation
        anmPowerOff(hpsScreen, 50);
    else
        // only hide the status window if
        // animation is off, because otherwise
        // we get screen display errors
        // (hpsScreen...)
        WinShowWindow(hwndShutdownStatus, FALSE);

    // now, this is fun:
    // here's a fine example of how to completely
    // block the system without any chance to escape.
    // Since we have called DosShutdown(), all file
    // access is blocked already. In addition, we
    // do the following:
    // -- show "Press C-A-D..." window
    WinShowWindow(hwndCADMessage, TRUE);
    // -- make the CAD message system-modal
    WinSetSysModalWindow(HWND_DESKTOP, hwndCADMessage);
    // -- kill the tasklist (/helpers/winh.c)
    // so that Ctrl+Esc fails
    winhKillTasklist();
    // -- block all other WPS threads
    DosEnterCritSec();
    // -- and now loop forever!
    while (TRUE)
        DosSleep(10000);
}

/*
 *@@ xsdFinishStandardReboot:
 *      this finishes the shutdown procedure,
 *      rebooting the computer using the standard
 *      reboot procedure (DOS.SYS).
 *      There's no shutdown animation here.
 */

VOID xsdFinishStandardReboot(VOID)
{
    HFILE       hIOCTL;
    ULONG       ulAction;

    // if (optReboot), open DOS.SYS; this
    // needs to be done before DosShutdown() also
    *pulShutdownFunc2 = 2090;
    if (DosOpen("\\DEV\\DOS$", &hIOCTL, &ulAction, 0L,
        FILE_NORMAL,
        OPEN_ACTION_OPEN_IF_EXISTS,
        OPEN_SHARE_DENYNONE | OPEN_ACCESS_READWRITE,
        0L) != NO_ERROR)
    {
        DebugBox("XShutdown", "The DOS.SYS device driver could not be opened. "
                "XShutdown will be unable to reboot your computer. "
                "Please consult the XFolder Online Reference for a remedy of this problem.");
    }

    if (hfShutdownLog) {
        sprintf(szLog, "xsdFinishStandardReboot: Opened DOS.SYS, hFile: 0x%lX\n", hIOCTL);
        doshWriteToLogFile(hfShutdownLog, szLog);
        sprintf(szLog, "xsdFinishStandardReboot: Calling DosShutdown(0), closing log.\n");
        doshWriteToLogFile(hfShutdownLog, szLog);
        DosClose(hfShutdownLog);
        hfShutdownLog = NULLHANDLE;
    }

    DosShutdown(0);

    *pulShutdownFunc2 = 2097;

    // say "Rebooting..."
    WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
            pNLSStrings->pszSDRebooting);

    // restart the machine via DOS.SYS (opened above)
    DosSleep(500);
    DosDevIOCtl(hIOCTL, 0xd5, 0xab, NULL, 0, NULL, NULL, 0, NULL);

    // don't know if this function returns, but
    // we better make sure we don't return from this
    // function
    while (TRUE)
        DosSleep(10000);
}

/*
 *@@ xsdFinishUserReboot:
 *      this finishes the shutdown procedure,
 *      rebooting the computer by starting a
 *      user reboot command.
 *      There's no shutdown animation here.
 */

VOID xsdFinishUserReboot(VOID)
{
    // user reboot item: in this case, we don't call
    // DosShutdown(), which is supposed to be done by
    // the user reboot command
    CHAR    szTemp[CCHMAXPATH];
    PID     pid;
    ULONG   sid;

    sprintf(szTemp,
            pNLSStrings->pszStarting, psdParams->szRebootCommand);
    WinSetDlgItemText(hwndShutdownStatus, ID_SDDI_STATUS,
            szTemp);

    if (hfShutdownLog) {
        sprintf(szLog,
            "Trying to start user reboot cmd: %s, closing log.\n",
            psdParams->szRebootCommand);
        doshWriteToLogFile(hfShutdownLog, szLog);
        DosClose(hfShutdownLog);
        hfShutdownLog = NULLHANDLE;
    }

    sprintf(szTemp, "/c %s", psdParams->szRebootCommand);
    if (doshQuickStartSession("cmd.exe", szTemp,
        FALSE, // show flag
        TRUE,  // wait flag
        &sid, &pid) != NO_ERROR)
    {
        DebugBox("XShutdown", "The user-defined restart command failed. "
            "We will now restart the WPS.");
        xsdRestartWPS();
    }
    else
        // no error:
        // we'll always get here, since the user reboot
        // is now taking place in a different process, so
        // we just stop here
        while (TRUE)
            DosSleep(10000);
}

/*
 *@@ xsdFinishAPMPowerOff:
 *      this finishes the shutdown procedure,
 *      using the functions in apm.c.
 */

VOID xsdFinishAPMPowerOff(VOID)
{
    CHAR        szAPMError[500];
    ULONG       ulrcAPM = 0;

    // prepare APM power off
    ulrcAPM = apmPreparePowerOff(szAPMError);
    if (hfShutdownLog) {
        sprintf(szLog, "xsdFinishAPMPowerOff: apmPreparePowerOff returned 0x%lX\n",
                ulrcAPM);
        doshWriteToLogFile(hfShutdownLog, szLog);
    }

    if (ulrcAPM & APM_IGNORE) {
        // if this returns APM_IGNORE, we continue
        // with the regular shutdown w/out APM
        if (hfShutdownLog) {
            sprintf(szLog, "APM_IGNORE, continuing with normal shutdown\n",
                    ulrcAPM);
            doshWriteToLogFile(hfShutdownLog, szLog);
        }
        xsdFinishStandardMessage();
        // this does not return
    }
    else if (ulrcAPM & APM_CANCEL) {
        // APM_CANCEL means cancel shutdown
        WinShowPointer(HWND_DESKTOP, TRUE);
        if (hfShutdownLog) {
            sprintf(szLog, "  Error message: %s",
                    szAPMError);
            doshWriteToLogFile(hfShutdownLog, szLog);
        }
        cmnMessageBox(HWND_DESKTOP, "APM Error", szAPMError,
                MB_CANCEL | MB_SYSTEMMODAL);
        // restart WPS
        xsdRestartWPS();
        // this does not return
    }
    // else: APM_OK means preparing went alright

    if (ulrcAPM & APM_DOSSHUTDOWN_0)
    {
        // shutdown request by apm.c:
        if (hfShutdownLog) {
            sprintf(szLog, "xsdFinishAPMPowerOff: Calling DosShutdown(0), closing log.\n");
            doshWriteToLogFile(hfShutdownLog, szLog);
            DosClose(hfShutdownLog);
            hfShutdownLog = NULLHANDLE;
        }

        DosShutdown(0);
    }
    // if apmPreparePowerOff requested this,
    // do DosShutdown(1)
    else if (ulrcAPM & APM_DOSSHUTDOWN_1)
    {
        if (hfShutdownLog) {
            sprintf(szLog, "xsdFinishAPMPowerOff: Calling DosShutdown(1), closing log.\n");
            doshWriteToLogFile(hfShutdownLog, szLog);
            DosClose(hfShutdownLog);
            hfShutdownLog = NULLHANDLE;
        }

        DosShutdown(1);

    }
    // or if apmPreparePowerOff requested this,
    // do no DosShutdown()
    if (hfShutdownLog) {
        DosClose(hfShutdownLog);
        hfShutdownLog = NULLHANDLE;
    }

    if (psdParams->optAnimate)
        // cute power-off animation
        anmPowerOff(hpsScreen, 50);
    else
        // only hide the status window if
        // animation is off, because otherwise
        // we get screen display errors
        // (hpsScreen...)
        WinShowWindow(hwndShutdownStatus, FALSE);

    // if APM power-off was properly prepared
    // above, this flag is still TRUE; we
    // will now call the function which
    // actually turns the power off
    apmDoPowerOff();
    // we do _not_ return from that function
}

/* ******************************************************************
 *                                                                  *
 *   here comes the Update thread                                   *
 *                                                                  *
 ********************************************************************/

/*
 *@@ xsd_fntUpdateThread:
 *          this thread is responsible for monitoring the window list
 *          while XShutdown is running and windows are being closed.
 *          It builds an internal second PSHUTLISTITEM linked list
 *          every 100 ms and then compares it to the global pliShutdownFirst;
 *          if they are different, fnwpShutdown is posted a message
 *          so that it can rebuild pliShutdownFirst and update the windows.
 */

void _Optlink xsd_fntUpdateThread(PVOID ptiMyself)
{
    HAB             habUpdateThread;
    HMQ             hmqUpdateThread;
    ULONG           ulShutItemCount = 0,
                    ulTestItemCount = 0;
    BOOL            fSemOwned = FALSE;

    BOOL            fUpdated = FALSE;
    APIRET          rc = NO_ERROR;
    PSZ             pszErrMsg = NULL;
    PTHREADGLOBALS pThreadGlobals = xthrQueryGlobals();

    TRY_LOUD(excpt1)
    {
        DosSetPriority(PRTYS_THREAD,
                      PRTYC_REGULAR,
                       -31,          // priority delta
                       0);           // this thread

        if (habUpdateThread = WinInitialize(0))
        {
            if (hmqUpdateThread = WinCreateMsgQueue(habUpdateThread, 0))
            {
                PSHUTLISTITEM   pTestList = NULL,
                                pTestLastItem = NULL;

                WinCancelShutdown(hmqUpdateThread, TRUE);

                // wait until main shutdown window is up
                while ( (hwndMain == 0) && (!(pThreadGlobals->ptiUpdateThread->fExit)) )
                    DosSleep(100);

                while (!(pThreadGlobals->ptiUpdateThread->fExit)) {
                    // this is the first loop: we arrive here every time
                    // the task list has changed */
                    #ifdef DEBUG_SHUTDOWN
                        _Pmpf(( "UT: Waiting for update..." ));
                    #endif

                    // have Shutdown thread update its list of items then
                    WinPostMsg(hwndMain, WM_COMMAND, MPFROM2SHORT(ID_SDMI_UPDATESHUTLIST, 0),
                        MPNULL);
                    fUpdated = FALSE;

                    // now wait until Shutdown thread is done updating its
                    // list; it then posts an event semaphore
                    while (     (!fUpdated)
                            &&  (!(pThreadGlobals->ptiUpdateThread->fExit))
                          )
                    {
                        if (pThreadGlobals->ptiUpdateThread->fExit) {
                            // we're supposed to exit:
                            fUpdated = TRUE;
                            #ifdef DEBUG_SHUTDOWN
                                _Pmpf(( "UT: Exit recognized" ));
                            #endif
                        }
                        else {
                            ULONG   ulUpdate;
                            // query event semaphore post count
                            DosQueryEventSem(hevUpdated, &ulUpdate);
                            fUpdated = (ulUpdate > 0);
                            #ifdef DEBUG_SHUTDOWN
                                _Pmpf(( "UT: update recognized" ));
                            #endif
                            DosSleep(100);
                        }
                    } // while (!fUpdated);

                    ulTestItemCount = 0;
                    ulShutItemCount = 0;

                    #ifdef DEBUG_SHUTDOWN
                        _Pmpf(( "UT: Waiting for task list change, loop 2..." ));
                    #endif

                    while (     (ulTestItemCount == ulShutItemCount)
                             && (!(pThreadGlobals->ptiUpdateThread->fExit))
                          )
                    {
                        // this is the second loop: we stay in here until the
                        // task list has changed; for monitoring this, we create
                        // a second task item list similar to the pliShutdownFirst
                        // list and compare the two */
                        if (pTestList)
                            lstClear((PLISTITEM*)&pTestList, NULL);

                        // create a test list for comparing the task list;
                        // this is our private list, so we need no mutex
                        // semaphore
                        xsdBuildShutList(&pTestList, NULL);

                        // count items in the test list
                        ulTestItemCount = lstCountItems((PLISTITEM)pTestList);

                        // count items in the list of the Shutdown thread;
                        // here we need a mutex semaphore, because the
                        // Shutdown thread might be working on this too
                        fSemOwned = (DosRequestMutexSem(hmtxShutdown, 4000) == NO_ERROR);
                        if (fSemOwned) {
                            ulShutItemCount = lstCountItems((PLISTITEM)pliShutdownFirst);
                            DosReleaseMutexSem(hmtxShutdown);
                            fSemOwned = FALSE;
                        }

                        if (!(pThreadGlobals->ptiUpdateThread->fExit))
                            DosSleep(100);
                    } // end while; loop until either the Shutdown thread has set the
                      // Exit flag or the list has changed

                    #ifdef DEBUG_SHUTDOWN
                        _Pmpf(( "UT: Change or exit recognized" ));
                    #endif
                }  // end while; loop until exit flag set

                #ifdef DEBUG_SHUTDOWN
                    _Pmpf(( "UT: Exiting..." ));
                #endif

                lstClear((PLISTITEM*)&pTestList, (PLISTITEM*)&pTestLastItem);
                WinDestroyMsgQueue(hmqUpdateThread);
            }
            WinTerminate(habUpdateThread);
        }
    }
    CATCH(excpt1)
    {
        HWND hwndDesktop;
        if (fSemOwned) {
            DosReleaseMutexSem(hmtxShutdown);
            fSemOwned = FALSE;
        }

        hwndDesktop = _wpclsQueryActiveDesktopHWND(_WPDesktop);
        if (    (hwndDesktop)
             && (pszErrMsg == NULL)
           )
        {
            if (fSemOwned) {
                DosReleaseMutexSem(hmtxShutdown);
                fSemOwned = FALSE;
            }

            // only report the first error, or otherwise we will
            // jam the system with msg boxes
            *pulShutdownFunc2 = 516;
            pszErrMsg = malloc(1000);
            if (pszErrMsg) {
                strcpy(pszErrMsg, "An error occured in the XFolder Update thread. "
                        "In the root directory of your boot drive, you will find a "
                        "file named XFLDTRAP.LOG, which contains debugging information. "
                        "If you had shutdown logging enabled, you will also find the "
                        "file XSHUTDWN.LOG there. If not, please enable shutdown "
                        "logging in the Desktop's settings notebook. ");
                xthrPostWorkplaceObjectMsg(XOM_EXCEPTIONCAUGHT, (MPARAM)pszErrMsg,
                        (MPARAM)0); // don't enforce WPS restart

                if (hfShutdownLog) {
                    doshWriteToLogFile(hfShutdownLog, "\n*** CRASH\n");
                    doshWriteToLogFile(hfShutdownLog, pszErrMsg);
                    doshWriteToLogFile(hfShutdownLog, "\n");
                }
            }
        }
    } END_CATCH;

    thrGoodbye((PTHREADINFO)ptiMyself);
    #ifdef DEBUG_SHUTDOWN
        DosBeep(100, 100);
    #endif
}


