
/*
 *@@sourcefile xthreads.c:
 *      this file contains XFolder thread handling.
 *      Currently, there are three additional object
 *      windows which are _always_ running:
 *
 *      --  XFolder Workplace object window (fnwpXFolderObject)
 *      --  XFolder "Worker" thread / object window (fntWorkerThread, fnwpWorkerObject)
 *      --  XFolder "Quick" thread / object window (fntQuickThread, fnwpQuickObject)
 *
 *      plus functions to communicate with these threads.
 *
 *@@include #define INCL_DOSSEMAPHORES
 *@@include #define INCL_DOSDATETIME
 *@@include #define INCL_DOSPROCESS
 *@@include #define INCL_WINWINDOWMGR
 *@@include #define INCL_WINPOINTERS
 *@@include #include <os2.h>
 *@@include #include <wpobject.h>
 *@@include #include "common.h"
 *@@include #include "xthreads.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_DOSMODULEMGR
#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_WINWINDOWMGR
#define INCL_WINDIALOGS
#define INCL_WINFRAMEMGR        // WM_FORMATFRAME, SC_CLOSE etc.
#define INCL_WINTIMER
#define INCL_WINSTDCNR          // needed for winh.h
#define INCL_WINSHELLDATA       // profile funcs

#define INCL_WININPUT           // ***
#define INCL_WINTRACKRECT       // ***
#define INCL_WINPOINTERS        // ***

#define INCL_GPILOGCOLORTABLE
#define INCL_GPIPRIMITIVES

#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
#include <io.h>

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

#include "animate.h"            // icon and other animations
#include "eas.h"                // extended attributes helper routines
#include "linklist.h"           // linked list helper routines
#include "progbars.h"           // progress bar control
#include "shapewin.h"           // shaped windows helper functions
#include "stringh.h"            // string helper routines

// SOM headers which don't crash with prec. header files
#pragma hdrstop                 // VAC++ keeps crashing otherwise
#include "xfldr.h"
#include "xfdesk.h"

// 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 "module.h"             // XFolder main DLL information
#include "sound.h"              // declarations for SOUND.DLL
#include "xshutdwn.h"           // XFolder eXtended Shutdown
#include "xthreads.h"           // XFolder threads; this includes threads.h

// other SOM headers
#include <wpshadow.h>           // WPShadow

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

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

// global structure with data needed across threads
// (see xthreads.h)
THREADGLOBALS       ThreadGlobals = {0};

// "Quick open" dlg status (Workplace object wnd)
ULONG               ulQuickOpenNow = 0,
                    ulQuickOpenMax = 0;
HWND                hwndQuickStatus = NULLHANDLE;
BOOL                fQuickOpenCancelled = FALSE;

// currently waiting messages for Worker thread;
// if this gets too large, its priority will be raised
ULONG               ulWorkerMsgCount = 0;
BOOL                fSetting = FALSE;
HAB                 habWorkerThread = NULLHANDLE;
HMQ                 hmqWorkerThread = NULLHANDLE;
// flags for whether the Worker thread owns semaphores
BOOL                fWorkerAwakeObjectsSemOwned = FALSE;

// Quick thread
HAB                 habQuickThread = NULLHANDLE;
HMQ                 hmqQuickThread = NULLHANDLE;
CHAR                szBootupStatus[256];
HWND                hwndBootupStatus = NULLHANDLE;

// SOUND.DLL module handle
HMODULE             hmodSoundDLL = NULLHANDLE;
// imported functions (see sound.h)
PFN_SNDOPENSOUND    psndOpenSound = NULL;
PFN_SNDPLAYSOUND    psndPlaySound = NULL;
PFN_SNDSTOPSOUND    psndStopSound = NULL;

// forwared declarations
MRESULT EXPENTRY fnwpStartupDlg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
MRESULT EXPENTRY fncbStartup(HWND hwndStatus, ULONG ulObject, MPARAM mpNow, MPARAM mpMax);
MRESULT EXPENTRY fnwpQuickOpenDlg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2);
MRESULT EXPENTRY fncbQuickOpen(HWND hwndFolder, ULONG ulObject, MPARAM mpNow, MPARAM mpMax);

/* ******************************************************************
 *                                                                  *
 *   Global interface                                               *
 *                                                                  *
 ********************************************************************/

/*
 *@@ xthrQueryGlobals:
 *      this returns the global THREADGLOBALS structure
 *      which contains all kinds of data which need to
 *      be accessed across threads. This structure is
 *      a global structure in xthreads.c.
 *      Note: access to this structure is not generally
 *      serialized. Most of this data is initialized and
 *      updated by the xthr* functions in xthreads.c.
 *      You should therefore only _read_ that data, not
 *      write to it.
 */

PTHREADGLOBALS xthrQueryGlobals(VOID)
{
    return (&ThreadGlobals);
}

/* ******************************************************************
 *                                                                  *
 *   Workplace object window                                        *
 *                                                                  *
 ********************************************************************/

BOOL     fLimitMsgOpen = FALSE;

/*
 *@@ fnwpXFolderObject:
 *      wnd proc for the thread-1 object window.
 *
 *      This is needed for processing messages which must be
 *      processed on thread 1. We cannot however process these
 *      messages in the subclassed folder frame wnd proc
 *      (fnwpSubclassedFolderFrame in xfldr.c),
 *      because adding user messages to that wnd proc could
 *      conflict with default WPFolder messages or those of
 *      some other WPS enhancer.
 *
 *      We therefore create another object window, which we
 *      own all alone.
 *
 *      Even though the PM docs say that an object window should never
 *      be created on thread 1 (which we're doing here), because this
 *      would "slow down the system", this is not generally true.
 *      WRT performance, it doesn't matter if the object window or a
 *      frame window processes messages. And since we _need_ to process
 *      some messages on the Workplace thread (1), especially when
 *      manipulating WPS windows, we do it here.
 *
 *      But of course, since this is on thread 1, we must get out of
 *      here quickly (0.1 seconds rule), because while we're processing
 *      something in here, the WPS user interface ist blocked.
 *
 *      Note: Another view-specific object window is created
 *      for every folder view that is opened, because sometimes
 *      folder views do _not_ run on thread 1 and manipulating
 *      frame controls from thread 1 would then hang the PM.
 *      See fnwpSupplObject in xfldr.c for details.
 */

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

    switch(msg) {

        /*
         * XOM_POCCALLBACK:
         *      this msg is posted from the Worker thread whenever
         *      a callback func needs to be called during those
         *      "process ordered content" (POC) functions (initiated by
         *      xfProcessOrderedContent); we need this message to
         *      have the callback func run on the folder's thread.
         *      Parameters:
         *          PPROCESSCONTENTINFO mp1
         *                          structure with all the information;
         *                          this routine must set the hwndView field
         *          MPARAM mp2      always NULL
         */

        case XOM_POCCALLBACK: {
            PPROCESSCONTENTINFO ppci = (PPROCESSCONTENTINFO)mp1;
            if (ppci) {
                ppci->hwndView = (HWND)((*(ppci->pfnwpCallback))(ppci->ulCallbackParam,
                    (ULONG)ppci->pObject,
                    (MPARAM)ppci->ulObjectNow, (MPARAM)ppci->ulObjectMax));
            }
        break; }

        /*
         * XOM_BEGINSTARTUP:
         *      this is an XFolder msg posted by the Worker thread after
         *      the Desktop has been populated; this performs initialization
         *      of the Startup folder process, which will then be further
         *      performed by (again) the Worker thread
         */

        case XOM_BEGINSTARTUP: {
            PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
            XFolder         *pStartupFolder;

            #ifdef DEBUG_STARTUP
                DosBeep(1000, 1000);
            #endif

            if (!doshQueryShiftState())
            {
                if (pStartupFolder = _wpclsQueryFolder(_WPFolder, XFOLDER_STARTUPID, TRUE))
                {
                    // startup folder exists: create status window w/ progress bar,
                    // start folder content processing in worker thread
                    CHAR szTitle[200];
                    ULONG hPOC;
                    HWND hwndStartupStatus = WinLoadDlg(HWND_DESKTOP, NULLHANDLE,
                                    fnwpStartupDlg,
                                    NLS_MODULE,
                                    ID_XFD_STARTUPSTATUS,
                                    NULL);
                    if (pGlobalSettings->ShowStartupProgress) {
                        // get last window position from INI
                        winhRestoreWindowPos(hwndStartupStatus,
                                HINI_USER,
                                INIAPP_XFOLDER, INIKEY_WNDPOSSTARTUP,
                                // move only, no resize
                                SWP_MOVE | SWP_SHOW | SWP_ACTIVATE);
                        #ifdef DEBUG_STARTUP
                            DosBeep(2000, 1000);
                        #endif
                    }

                    hPOC = _xfBeginProcessOrderedContent(pStartupFolder,
                            pGlobalSettings->ulStartupDelay,
                            &fncbStartup,
                            (ULONG)hwndStartupStatus);

                    WinSetWindowULong(hwndStartupStatus, QWL_USER, hPOC);
                }
                else
                    // startup folder does not exist:
                    xthrPostWorkerMsg(WOM_DONEWITHSTARTUP, MPNULL, MPNULL);
            }
            else {
                DosBeep(1000, 300);
                // shift pressed: skip startup
                xthrPostWorkerMsg(WOM_DONEWITHSTARTUP, MPNULL, MPNULL);
            }
        break; }

        /*
         * XOM_BEGINQUICKOPEN:
         *      this is posted by the Worker thread after the startup
         *      folder has been processed; we will now go for the
         *      "Quick Open" folders by populating them and querying
         *      all their icons. If this is posted for the first time,
         *      mp1 must be NULL; for subsequent postings, mp1 contains the
         *      folder to be populated or (-1) if it's the last.
         */

        case XOM_BEGINQUICKOPEN: {
            PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
            BOOL fWorkToDo = FALSE;

            if (mp1 == NULL) {
                // first posting: prepare
                if (!doshQueryShiftState())
                {
                    XFolder *pQuick = NULL;

                    // count quick-open folders
                    ulQuickOpenNow = 0;
                    ulQuickOpenMax = 0;
                    for (pQuick = _xfclsQueryQuickOpenFolder(_XFolder, NULL);
                         pQuick;
                         pQuick = _xfclsQueryQuickOpenFolder(_XFolder, pQuick))
                    {
                        ulQuickOpenMax++;
                    }

                    if (ulQuickOpenMax)
                    {
                        fWorkToDo = TRUE;
                        // if we have any quick-open folders: go
                        if (pGlobalSettings->ShowStartupProgress) {
                            PNLSSTRINGS pNLSStrings = cmnQueryNLSStrings();
                            hwndQuickStatus = WinLoadDlg(HWND_DESKTOP, NULLHANDLE,
                                            fnwpQuickOpenDlg,
                                            NLS_MODULE,
                                            ID_XFD_STARTUPSTATUS,
                                            NULL);
                            WinSetWindowText(hwndQuickStatus,
                                    pNLSStrings->pszQuickStatus);

                            winhRestoreWindowPos(hwndQuickStatus,
                                        HINI_USER,
                                        INIAPP_XFOLDER, INIKEY_WNDPOSSTARTUP,
                                        SWP_MOVE | SWP_SHOW | SWP_ACTIVATE);
                            #ifdef DEBUG_STARTUP
                                DosBeep(2000, 1000);
                            #endif
                            WinSendMsg(WinWindowFromID(hwndQuickStatus, ID_SDDI_PROGRESSBAR),
                                        WM_UPDATEPROGRESSBAR,
                                        (MPARAM)0, (MPARAM)ulQuickOpenMax);
                            fQuickOpenCancelled = FALSE;
                        }

                        // get first quick-open folder
                        pQuick = _xfclsQueryQuickOpenFolder(_XFolder, NULL);
                        if (pQuick)
                            xthrPostWorkplaceObjectMsg(XOM_BEGINQUICKOPEN,
                                    (MPARAM)pQuick,
                                    MPNULL);
                        // else none defined: do nothing
                    }
                }
                else {
                    // shift pressed: skip startup
                    DosBeep(1000, 300);
                }
            } // end if (mp1 == NULL)
            else
            {
                // subsequent postings
                XFolder *pQuick = (XFolder*)mp1;

                if (    (((ULONG)pQuick) != -1)
                    &&  (!fQuickOpenCancelled)
                   )
                {
                    fWorkToDo = TRUE;
                    // subsequent postings: mp1 contains folder
                    if (pGlobalSettings->ShowStartupProgress)
                    {
                        CHAR szTemp[256];

                        _wpQueryFilename(pQuick, szTemp, TRUE);
                        WinSetDlgItemText(hwndQuickStatus, ID_SDDI_STATUS, szTemp);
                    }

                    // populate object in Worker thread
                    xthrPostWorkerMsg(WOM_QUICKOPEN,
                            (MPARAM)pQuick,
                            (MPARAM)fncbQuickOpen);
                    // the Worker thread will then post
                    // XOM_NEXTQUICKOPEN when it's done
                } else {
                    DosSleep(500);
                    // -1 => no more folders: clean up
                    if (pGlobalSettings->ShowStartupProgress) {
                        winhSaveWindowPos(hwndQuickStatus,
                                HINI_USER,
                                INIAPP_XFOLDER, INIKEY_WNDPOSSTARTUP);
                        WinDestroyWindow(hwndQuickStatus);
                    }
                }
            }

            if (!fWorkToDo)
                // no more folders to go:
                xthrPostQuickMsg(QM_DESTROYLOGO, 0, 0);
        break; }

        case XOM_NEXTQUICKOPEN: {
            PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
            XFolder *pQuick = (XFolder*)mp1;

            if (pQuick) {
                ulQuickOpenNow++;

                if (pGlobalSettings->ShowStartupProgress) {
                    WinSendMsg(WinWindowFromID(hwndQuickStatus, ID_SDDI_PROGRESSBAR),
                        WM_UPDATEPROGRESSBAR,
                        (MPARAM)(ulQuickOpenNow*100),
                        (MPARAM)(ulQuickOpenMax*100));
                }

                // get next quick-open folder
                pQuick = _xfclsQueryQuickOpenFolder(_XFolder, pQuick);
                xthrPostWorkplaceObjectMsg(XOM_BEGINQUICKOPEN,
                        (pQuick != NULL)
                                ? (MPARAM)pQuick
                                // last folder reached: cleanup signal
                                : (MPARAM)-1,
                        MPNULL);
                break;
            }

            // error or cancelled:
            if (pGlobalSettings->ShowStartupProgress)
                WinPostMsg(hwndQuickStatus, WM_CLOSE, MPNULL, MPNULL);
        break; }

        /*
         * XOM_LIMITREACHED:
         *      this is posted by cmnAppendMi2List when too
         *      many menu items are in use, i.e. the user has
         *      opened a zillion folder content menus; we
         *      will display a warning dlg, which will also
         *      destroy the open menu
         */

        case XOM_LIMITREACHED: {
            if (!fLimitMsgOpen) {
                // avoid more than one dlg window
                fLimitMsgOpen = TRUE;
                cmnSetHelpPanel(ID_XFH_LIMITREACHED);
                WinDlgBox(HWND_DESKTOP,         // parent is desktop
                      HWND_DESKTOP,             // owner is desktop
                      (PFNWP)fnwpDlgGeneric,    // dialog procedure, defd. at bottom
                      NLS_MODULE,  // from resource file
                      ID_XFD_LIMITREACHED,        // dialog resource id
                      (PVOID)NULL);             // no dialog parameters
                fLimitMsgOpen = FALSE;
            }
        break; }

        /*
         * XOM_EXCEPTIONCAUGHT:
         *      this is posted from the various XFolder threads
         *      when something trapped; it is assumed that
         *      mp1 is a PSZ to an error msg allocated with
         *      malloc(), and after displaying the error,
         *      (PSZ)mp1 is freed here. If mp2 != NULL, the WPS will
         *      be restarted (this is demanded by XSHutdown traps).
         */

        case XOM_EXCEPTIONCAUGHT: {
            if (mp1) {
                if (mp2) {
                    // restart WPS: Yes/No box
                    if (WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
                                  (PSZ)mp1, (PSZ)"XFolder: Exception caught",
                                  0, MB_YESNO | MB_ICONEXCLAMATION | MB_MOVEABLE)
                             == MBID_YES)
                        // if yes: terminate the current process,
                        // which is PMSHELL.EXE. We cannot use DosExit()
                        // directly, because this might mess up the
                        // C runtime library.
                        exit(99);
                } else
                    // just report:
                    DebugBox("XFolder: Exception caught", (PSZ)mp1);
                free((PSZ)mp1);
            }
        break; }

        /*
         * XOM_QUERYXFOLDERVERSION:
         *      this msg may be send to the XFolder object
         *      window from external programs to query the
         *      XFolder version number which is currently
         *      installed. We will return:
         *          mrc = MPFROM2SHORT(major, minor)
         *      which may be broken down by the external
         *      program using the SHORT1/2FROMMP macros.
         *      To get the handle of the XFolder object
         *      window, use
         *      WinWindowFromID(HWND_OBJECT, ID_XFOLDEROBJECT).
         *      This is used by the XShutdown command-line
         *      interface (XSHUTDWN.EXE) to assert that
         *      XFolder is up and running.
         */

        case XOM_QUERYXFOLDERVERSION: {
            mrc = MPFROM2SHORT(XFOLDER_VERSION_MAJOR, XFOLDER_VERSION_MINOR);
            // these are defined in dlgids.h
        break; }

        /*
         * XOM_EXTERNALSHUTDOWN:
         *      this msg may be posted to the XFolder object
         *      window from external programs to initiate
         *      the eXtended shutdown. mp1 is assumed to
         *      point to a block of shared memory containing
         *      a SHUTDOWNPARAMS structure.
         */

        case XOM_EXTERNALSHUTDOWN: {
            PSHUTDOWNPARAMS psdp = (PSHUTDOWNPARAMS)mp1;
            if ((ULONG)mp2 != 1234) {
                APIRET arc = DosGetSharedMem(psdp, PAG_READ | PAG_WRITE);
                if (arc == NO_ERROR) {
                    WinPostMsg(hwndObject, XOM_EXTERNALSHUTDOWN, mp1, (MPARAM)1234);
                    mrc = (MPARAM)TRUE;
                } else {
                    DebugBox("External XShutdown call", "Error calling DosGetSharedMem.");
                    mrc = (MPARAM)FALSE;
                }
            } else {
                // second call
                xsdInitiateShutdownExt(psdp);
                DosFreeMem(psdp);
            }
        break; }

        default:
            mrc = WinDefWindowProc(hwndObject, msg, mp1, mp2);
    }
    return (mrc);
}

/*
 *@@ xthrPostWorkplaceObjectMsg:
 *      post msg to thread-1 object window (fnwpXFolderObject).
 *      This is used from all kinds of places
 *      and different threads.
 */

BOOL xthrPostWorkplaceObjectMsg(ULONG msg, MPARAM mp1, MPARAM mp2)
{
    BOOL rc = FALSE;

    if (ThreadGlobals.hwndWorkplaceObject) {
        rc = WinPostMsg(ThreadGlobals.hwndWorkplaceObject, msg, mp1, mp2);
    }
    return (rc);
}

/*
 *@@ xthrSendWorkplaceObjectMsg:
 *      send msg to thread-1 object window (fnwpXFolderObject).
 *      Note that sending a message from another
 *      thread will block that thread until we return.
 */

MRESULT xthrSendWorkplaceObjectMsg(ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc = MPNULL;

    if (ThreadGlobals.hwndWorkplaceObject) {
        mrc = WinSendMsg(ThreadGlobals.hwndWorkplaceObject, msg, mp1, mp2);
    }
    return (mrc);
}

/*
 *@@ fnwpStartupDlg:
 *      dlg proc for the Startup status window, which
 *      runs on the main PM thread.
 */

MRESULT EXPENTRY fnwpStartupDlg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc;

    switch (msg) {

        case WM_INITDLG: {
            // WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
                // we don't need this here, it's done by fnwpXFolderObject
            pbarProgressBarFromStatic(WinWindowFromID(hwnd, ID_SDDI_PROGRESSBAR),
                            PBA_ALIGNCENTER | PBA_BUTTONSTYLE);
            mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
        break; }

        case WM_COMMAND:
            switch (SHORT1FROMMP(mp1)) {

                case DID_CANCEL: {
                    PPROCESSCONTENTINFO pPCI;
                    pPCI = (PPROCESSCONTENTINFO)WinQueryWindowULong(hwnd, QWL_USER);
                    if (pPCI)
                        pPCI->fCancelled = TRUE;
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
                break; }

                default:
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
            }
        break;

        case WM_SYSCOMMAND: {
            switch (SHORT1FROMMP(mp1)) {
                case SC_CLOSE:
                case SC_HIDE: {
                    PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
                    pGlobalSettings->ShowStartupProgress = 0;
                    PrfWriteProfileData(HINI_USERPROFILE, INIAPP_XFOLDER, INIKEY_GLOBALSETTINGS,
                                pGlobalSettings, sizeof(GLOBALSETTINGS));
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
                break; }

                default:
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
            }
        break; }

        default:
            mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
    }

    return (mrc);
}

/*
 *@@ fncbStartup:
 *      callback function for xfProcessOrderedContent
 *      during Startup folder processing;
 *      updates the status window and opens objects
 */

MRESULT EXPENTRY fncbStartup(HWND hwndStatus, ULONG ulObject, MPARAM mpNow, MPARAM mpMax)
{
    CHAR szStarting2[200], szTemp[200];
    HWND rc = 0;
    #ifdef DEBUG_STARTUP
        _Pmpf(("  -- fncbStartup called"));
    #endif

    if (ulObject) {
        PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();

        if (pGlobalSettings->ShowStartupProgress) {
            // update status text ("Starting xxx")
            // WinSetActiveWindow(HWND_DESKTOP, hwndStatus);
            strcpy(szTemp, _wpQueryTitle((WPObject*)ulObject));
            strhBeautifyTitle(szTemp);
            sprintf(szStarting2,
                (cmnQueryNLSStrings())->pszStarting,
                szTemp);
            WinSetDlgItemText(hwndStatus, ID_SDDI_STATUS, szStarting2);
        }

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

        /* if (pGlobalSettings->ShowStartupProgress)
            WinSetActiveWindow(HWND_DESKTOP, hwndStatus); */
        WinSendMsg(WinWindowFromID(hwndStatus, ID_SDDI_PROGRESSBAR),
            WM_UPDATEPROGRESSBAR, mpNow, mpMax);
    }
    else {
        // last object: close window
        winhSaveWindowPos(hwndStatus, HINI_USER, INIAPP_XFOLDER, INIKEY_WNDPOSSTARTUP);
        WinDestroyWindow(hwndStatus);
        // done with startup: notify Worker thread
        xthrPostWorkerMsg(WOM_DONEWITHSTARTUP, MPNULL, MPNULL);
    }
    return (MRESULT)rc;
}

/*
 *@@ fnwpQuickOpenDlg:
 *      dlg proc for the QuickOpen status window
 */

MRESULT EXPENTRY fnwpQuickOpenDlg(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
    MRESULT mrc;

    switch (msg) {

        case WM_INITDLG: {
            // WinSetWindowULong(hwnd, QWL_USER, (ULONG)mp2);
            // we don't need this here, it's done by fnwpXFolderObject
            pbarProgressBarFromStatic(WinWindowFromID(hwnd, ID_SDDI_PROGRESSBAR),
                            PBA_ALIGNCENTER | PBA_BUTTONSTYLE);
            mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
        break; }

        case WM_COMMAND:
            switch (SHORT1FROMMP(mp1)) {

                case DID_CANCEL: {
                    fQuickOpenCancelled = TRUE;
                    // this will cause the callback below to
                    // return FALSE
                break; }

                default:
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
            }
        break;

        case WM_SYSCOMMAND: {
            switch (SHORT1FROMMP(mp1)) {
                case SC_HIDE: {
                    PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
                    pGlobalSettings->ShowStartupProgress = 0;
                    PrfWriteProfileData(HINI_USERPROFILE, INIAPP_XFOLDER, INIKEY_GLOBALSETTINGS,
                                pGlobalSettings, sizeof(GLOBALSETTINGS));
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
                break; }

                default:
                    mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
            }
        break; }

        case WM_DESTROY: {
            mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
        break; }

        default:
            mrc = WinDefDlgProc(hwnd, msg, mp1, mp2);
    }

    return (mrc);
}

/*
 *@@ fncbQuickOpen:
 *      callback func called by Worker thread for
 *      each object whose icon is queried.
 *      If this returns FALSE, processing is
 *      terminated.
 */

MRESULT EXPENTRY fncbQuickOpen(HWND hwndFolder,
                            ULONG ulObject,     //
                            MPARAM mpNow,       // current object count
                            MPARAM mpMax)       // maximum object count
{
    WinSendMsg(WinWindowFromID(hwndQuickStatus, ID_SDDI_PROGRESSBAR),
            WM_UPDATEPROGRESSBAR,
            (MPARAM)(
                        ulQuickOpenNow*100 +
                            ( (100 * (ULONG)mpNow) / (ULONG)mpMax )
                    ),
            (MPARAM)(ulQuickOpenMax*100));

    // if "Cancel" has been pressed, return FALSE
    return ((MPARAM)(!fQuickOpenCancelled));
}

/* ******************************************************************
 *                                                                  *
 *   here come the XFolder Worker thread functions                  *
 *                                                                  *
 ********************************************************************/

/*
 *@@ xthrRaiseWorkerThreadPriority:
 *      depending on fRaise, raise or lower Worker
 *      thread prty
 */

BOOL xthrRaiseWorkerThreadPriority(BOOL fRaise)
{
    BOOL brc = FALSE;

    while (fSetting)
        DosSleep(1000);

    fSetting = TRUE;
    if (fRaise) {
        if (ThreadGlobals.WorkerThreadHighPriority == FALSE) {
            #ifdef DEBUG_PRIORITY
                DosBeep(1000, 10);
                _Pmpf(( "Raising Worker thread priority" ));
            #endif
            DosSetPriority(PRTYS_THREAD,
                           PRTYC_REGULAR,
                           PRTYD_MAXIMUM, // priority delta
                           thrQueryID(ThreadGlobals.ptiWorkerThread));
            ThreadGlobals.WorkerThreadHighPriority = TRUE;
            brc = TRUE;
        }
    } else {
        if (ThreadGlobals.WorkerThreadHighPriority == TRUE) {
            #ifdef DEBUG_PRIORITY
                DosBeep(100, 10);
                _Pmpf(( "Lowering Worker thread priority" ));
            #endif
            DosSetPriority(PRTYS_THREAD,
                           PRTYC_IDLETIME,
                           PRTYD_MAXIMUM, // priority delta

                           // PRTYC_IDLETIME,
                           // 0, // priority delta
                           0);
            brc = TRUE;
            ThreadGlobals.WorkerThreadHighPriority = FALSE;
        }
    }
    fSetting = FALSE;
    return (brc);
}

/*
 *@@ xthrPostWorkerMsg:
 *      this posts a msg to the XFolder Worker thread object window
 *      and controls this thread's priority at the same time; use this
 *      function instead of WinPostQueueMsg, cos otherwise the
 *      Worker thread gets confused
 */

BOOL xthrPostWorkerMsg(ULONG msg, MPARAM mp1, MPARAM mp2)
{
    BOOL rc = FALSE;

    if (thrQueryID(ThreadGlobals.ptiWorkerThread)) {
        if (ThreadGlobals.hwndWorkerObject) {
            rc = WinPostMsg(ThreadGlobals.hwndWorkerObject, msg, mp1, mp2);

            if (rc) {
                ulWorkerMsgCount++;
                if (ulWorkerMsgCount > 300)
                    // if the Worker thread msg queue gets congested, boost priority
                    xthrRaiseWorkerThreadPriority(TRUE);
            }
        }
    }
    return (rc);
}

/*
 *@@ xthrPostQuickMsg:
 *      this posts a msg to the XFolder Quick thread object window.
 */

BOOL xthrPostQuickMsg(ULONG msg, MPARAM mp1, MPARAM mp2)
{
    BOOL rc = FALSE;

    if (thrQueryID(ThreadGlobals.ptiQuickThread)) {
        if (ThreadGlobals.hwndQuickObject) {
            rc = WinPostMsg(ThreadGlobals.hwndQuickObject,
                        msg,
                        mp1,
                        mp2);
        }
    }
    return (rc);
}

/*
 *@@ xthrPlaySystemSound:
 *      this posts a msg to the XFolder Sound thread in
 *      SOUND.DLL to play a system sound. This should be
 *      failsafe, since successful loading of that DLL
 *      is checked for.
 *      usIndex may be one of the MMSOUND_* values in
 *      "sound.h".
 */

BOOL xthrPlaySystemSound(USHORT usIndex)
{
    BOOL rc = FALSE;

    if (ThreadGlobals.ulMMPM2Working == MMSTAT_WORKING)
    {
        // SOUND.DLL loaded successfully and
        // Quickthread running:
        rc = WinPostMsg(ThreadGlobals.hwndQuickObject,
                    QM_PLAYSYSTEMSOUND,
                    (MPARAM)usIndex,
                    MPNULL);
    }
    return (rc);
}

/*
 *@@ xthrIsPlayingSystemSound:
 *      returns TRUE if the Quick thread is
 *      currently playing a system sound.
 *      This is useful for waiting until it's done.
 */

BOOL xthrIsPlayingSystemSound(VOID)
{
    return (ThreadGlobals.usDeviceID != 0);
}

/*
 *@@ fnwpGenericStatus:
 *      dlg func for bootup status wnd and maybe others
 */

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

    switch (msg) {
        case WM_INITDLG:
            mrc = (MPARAM)TRUE; // focus change flag;
            // mrc = WinDefDlgProc(hwndDlg, msg, mp1, mp2);
        break;

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

    return (mrc);
}

/*
 *@@ fnwpWorkerObject:
 *      wnd proc for Worker thread object window
 *      (see fntWorkerThread below)
 */

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

    ThreadGlobals.ulWorkerFunc2 = 100;
    switch (msg) {

        /*
         * WM_CREATE:
         *      set a timer to periodically free storage
         */

        case WM_CREATE: {
            mrc = WinDefWindowProc(hwndObject, msg, mp1, mp2);
            WinStartTimer(habWorkerThread, hwndObject,
                            2,          // id
                            5*60*1000); // every five minutes
        break; }

        /*
         * WM_TIMER:
         */

        case WM_TIMER: {
            switch ((USHORT)mp1)  {// timer id

                // timer 2: free unused memory
                case 2:
                    #ifdef DEBUG_MEMORYBEEP
                        DosBeep(3000, 20);
                    #endif

                    _heapmin();
                    // _heapmin returns all unused memory from the default
                    // runtime heap to the operating system

                    #ifdef DEBUG_MEMORYBEEP
                        DosBeep(3500, 20);
                    #endif
                break;
            }
        break; }

        /*
         * WOM_DESKTOPREADY:
         *   this msg is posted after the Desktop has been opened;
         *   we will now loop until the Desktop has also been
         *   fully populated; if so, we will either display a
         *   "Welcome" msg or start processing the startup folder.
         *   This msg expects the currently active desktop in
         *   (XFldDesktop*)mp1.
         */

        case WOM_DESKTOPREADY: {
            ULONG ul;
            ULONG ulS = sizeof(ul);
            PID   pidWPSLast = 0xFFFF, pidWPSNow;
            CHAR  szPidWPSLast[10];
            CHAR szPIDFile[CCHMAXPATH];
            PSZ pszLastPID = "FFFF";

            ThreadGlobals.ulWorkerFunc2 = 1000;
            DosSleep(1000);

            if ((_wpQueryFldrFlags((XFolder*)mp1)
                        & FOI_POPULATEDWITHALL) == 0)
            {
                DosSleep(100);
                // if Desktop is not populated yet, post this
                // msg again
                xthrPostWorkerMsg(WOM_DESKTOPREADY, mp1, MPNULL);
                // do not continue
                break;
            }

            // ----- else: Desktop is populated, go on

            // if XFolder was just installed, check for
            // existence of config folders and
            // display welcome msg
            if (PrfQueryProfileInt(HINI_USER, INIAPP_XFOLDER,
                    INIKEY_JUSTINSTALLED,
                    0x123) != 0x123)
            {   // XFolder was just installed:
                // delete "just installed" INI key
                PrfWriteProfileString(HINI_USER, INIAPP_XFOLDER,
                        INIKEY_JUSTINSTALLED,
                        NULL);
                // even if the installation folder exists, create a
                // a new one
                // if (!_wpclsQueryFolder(_WPFolder, XFOLDER_MAINID, TRUE))
                    xthrPostWorkerMsg(WOM_RECREATECONFIGFOLDER,
                            (MPARAM)RCF_MAININSTALLFOLDER,
                            MPNULL);

                xthrPostWorkerMsg(WOM_WELCOME, MPNULL, MPNULL);
            }

            // initiate processing of startup folder; this is
            // again done by the Workplace thread (XFolder Object wnd).
            // We only do startup if the current process ID of the
            // WPS is <= the one which was stored in the file
            // "/bin/lastpid", when the WPS last started up.
            // This happens in two situations:
            // a)   the computer has been rebooted;
            // b)   the "Restart WPS" function has deleted the
            //      "lastpid" file because the user wants to have
            //      the Startup folder processed. In this case,
            //      we have the default 0xFFFF value from above
            //      for the last WPS PID.;-)
            // This will prevent the startup folder from being executed
            // when the WPS has crashed and restarted itself.

            // get last WPS PID from "lastpid" file
            ThreadGlobals.ulWorkerFunc2 = 1010;
            cmnQueryXFolderBasePath(szPIDFile);
            strcat(szPIDFile, "\\bin\\lastpid");
            ThreadGlobals.ulWorkerFunc2 = 1020;
            pszLastPID = doshReadTextFile(szPIDFile, 0);
            ThreadGlobals.ulWorkerFunc2 = 1030;
            if (pszLastPID) {
                sscanf(pszLastPID, "%lX", &pidWPSLast);
                free(pszLastPID);
            }

            // get current PID
            ThreadGlobals.ulWorkerFunc2 = 1040;
            WinQueryWindowProcess(hwndObject, &pidWPSNow, NULL);

            if (pidWPSNow <= pidWPSLast)
            {
                xthrPostWorkplaceObjectMsg(XOM_BEGINSTARTUP, MPNULL, MPNULL);
            } else {
                xthrPostWorkerMsg(WOM_DONEWITHSTARTUP, MPNULL, MPNULL);
            }

            ThreadGlobals.ulWorkerFunc2 = 1050;
            sprintf(szPidWPSLast, "%lX", pidWPSNow);
            ThreadGlobals.ulWorkerFunc2 = 1060;
            doshWriteTextFile(szPIDFile, szPidWPSLast,
                                FALSE); // no backup
            ThreadGlobals.ulWorkerFunc2 = 1070;
            /* PrfWriteProfileString(HINI_USERPROFILE, INIAPP_XFOLDER,
                            INIKEY_LASTPID, szPidWPSLast); */
        break; }

        /*
         * WOM_WELCOME:
         *     display hello dlg box
         */

        case WOM_WELCOME: {
            BOOL fDone = FALSE;
            if (doshIsWarp4())
            {
                // on Warp 4, open the SmartGuide window
                // which was created by the install script
                HOBJECT hobjIntro = WinQueryObject("<XFOLDER_INTRO>");
                if (hobjIntro)  {
                    WinOpenObject(hobjIntro, OPEN_DEFAULT, TRUE);
                    fDone = TRUE;
                }
            }

            if (!fDone)
            {
                // SmartGuide object not found (Warp 3):
                // display simple dialog
                ThreadGlobals.ulWorkerFunc2 = 3000;
                winhCenteredDlgBox(HWND_DESKTOP, HWND_DESKTOP,
                         WinDefDlgProc,
                         NLS_MODULE,
                         ID_XFD_WELCOME,
                         0);
            }
        break;}

        /*
         * WOM_DONEWITHSTARTUP:
         *      this is posted after processing of the startup
         *      folder has finished; we will now
         *      populate the config folders in the
         *      background
         */

        case WOM_DONEWITHSTARTUP: {
            XFolder *pFolder = _wpclsQueryFolder(_XFolder, XFOLDER_CONFIGID, TRUE);
            ThreadGlobals.ulWorkerFunc2 = 4000;
            if (pFolder)
                xwpsPopulateTree(pFolder);

            // now go for the "Quick open" folders
            xthrPostWorkplaceObjectMsg(XOM_BEGINQUICKOPEN, MPNULL, MPNULL);
        break; }

        /*
         * WOM_QUICKOPEN:
         *      this is posted by the XFolder Object window
         *      on the main PM thread for each folder with
         *      the QuickOpen flag on: we will populate it
         *      here and load all the icons.
         *      Parameters:
         *          XFolder* mp1    folder pointer
         *          ULONG    mp2    callback function (PFNWP)
         *      The callback will be called if mp2 != NULL
         *      with the following parameters:
         *          HWND    current folder
         *          ULONG   current object therein
         *          MPARAM  current object count
         *          MPARAM  maximum object count
         */

        case WOM_QUICKOPEN: {
            XFolder *pFolder = (XFolder*)mp1;

            #ifdef DEBUG_STARTUP
                _Pmpf(("WOM_QUICKOPEN %lX", mp1));
            #endif

            if (pFolder) {
                WPObject    *pObject;
                ULONG       ulNow = 0, ulMax = 0;
                BOOL        fContinue = TRUE;

                // populate folder
                xwpsCheckIfPopulated(pFolder);

                // count objects
                for (   pObject = _wpQueryContent(pFolder, NULL, (ULONG)QC_FIRST);
                        (pObject);
                        pObject = _wpQueryContent(pFolder, pObject, (ULONG)QC_NEXT)
                    )
                {
                    ulMax++;
                }

                // collect icons for all objects
                for (   pObject = _wpQueryContent(pFolder, NULL, (ULONG)QC_FIRST);
                        (pObject);
                        pObject = _wpQueryContent(pFolder, pObject, (ULONG)QC_NEXT)
                    )
                {
                    PFNWP pfncb = (PFNWP)mp2;
                    _wpQueryIcon(pObject);
                    if (pfncb) {
                        // callback
                        fContinue = (BOOL)
                                  (*(pfncb))
                                    (
                                        (HWND)pFolder,
                                        (ULONG)pObject,
                                        (MPARAM)ulNow,
                                        (MPARAM)ulMax
                                    );
                        if (!fContinue) {
                            pObject = NULL;
                            xthrPostWorkplaceObjectMsg(XOM_NEXTQUICKOPEN, NULL, MPNULL);
                            break;
                        }
                    }
                    ulNow++;
                }

                if ( (fContinue) && (mp2 != NULL) )
                {
                    // now go for the next folder
                    xthrPostWorkplaceObjectMsg(XOM_NEXTQUICKOPEN, mp1, MPNULL);
                }
            }
        break; }

        /*
         * WOM_RECREATECONFIGFOLDER:
         *    this msg is posted if the
         *    config folder was not found
         */

        case WOM_RECREATECONFIGFOLDER: {
            static BOOL fWarningOpen = FALSE;
            ULONG   ulAction = (ULONG)mp1;
            ULONG   ulReturn = ulAction;
            cmnSetHelpPanel(ID_XFH_NOCONFIG);

            ThreadGlobals.ulWorkerFunc2 = 2000;
            if (ulAction == RCF_QUERYACTION)
            {
                HWND  hwndDlg;

                if (fWarningOpen)
                    break;

                hwndDlg = WinLoadDlg(HWND_DESKTOP,
                                    HWND_DESKTOP,
                                    (PFNWP)fnwpDlgGeneric,
                                    NLS_MODULE,
                                    ID_XFD_NOCONFIG, // "not found" dialog
                                    (PVOID)NULL);
                fWarningOpen = TRUE;
                ulReturn = (WinProcessDlg(hwndDlg)
                           == DID_DEFAULT)
                           ?  RCF_DEFAULTCONFIGFOLDER
                           :  RCF_EMPTYCONFIGFOLDERONLY;
                WinDestroyWindow(hwndDlg);
                fWarningOpen = FALSE;
            }

            if (ulReturn == RCF_EMPTYCONFIGFOLDERONLY)
                WinCreateObject("WPFolder",             // now create new config folder w/ proper objID
                        "XFolder Configuration",
                        "OBJECTID=<XFOLDER_CONFIG>",
                        "<WP_DESKTOP>",                 // on desktop
                        CO_UPDATEIFEXISTS);
            else if (   (ulReturn == RCF_DEFAULTCONFIGFOLDER)
                     || (ulReturn == RCF_MAININSTALLFOLDER)
                    )
            {
                HWND    hwndCreating;
                CHAR    szPath[CCHMAXPATH], szPath2[CCHMAXPATH], szPath3[CCHMAXPATH];
                if (cmnQueryXFolderBasePath(szPath))
                {   // INI entry found: append "\" if necessary
                    PID     pid;
                    ULONG   sid;
                    sprintf(szPath2, "/c %s\\install\\%s%s.cmd",
                            szPath,
                            (ulReturn == RCF_DEFAULTCONFIGFOLDER)
                                ? "crobj"       // script for config folder only
                                : "instl",      // script for full installation folder
                                                // (used after first WPS bootup)
                            cmnQueryLanguageCode());
                    strcpy(szPath3, &(szPath2[3]));

                    // "creating config" window
                    hwndCreating = WinLoadDlg(HWND_DESKTOP, HWND_DESKTOP,
                            WinDefDlgProc,
                            NLS_MODULE,
                            ID_XFD_CREATINGCONFIG,
                            0);
                    WinShowWindow(hwndCreating, TRUE);
                    doshQuickStartSession("cmd.exe", szPath2,
                                FALSE, // don't show
                                TRUE,  // wait
                                &sid, &pid);

                    if (WinQueryObject(XFOLDER_CONFIGID) == NULLHANDLE) {
                        CHAR    szMsg[1000];
                        sprintf(szMsg, "Oooooops! XFolder could not find %s, which should have recreated "
                                       "your config folder. Please re-install XFolder.",
                                       szPath3);
                        DebugBox("XFolder", szMsg);
                    }
                    WinDestroyWindow(hwndCreating);
                }
            }
        break; }

        /*
         * WOM_PROCESSORDEREDCONTENT:
         *      this msg is posted by xfProcessOrderedContent to
         *      begin working our way through the contents of a
         *      folder; from here we will call a callback func
         *      which has been specified with xfProcessOrderedContent.
         *      Msg params:
         *          XFolder *mp1    folder to process
         *          PPROCESSCONTENTINFO
         *                  *mp2    structure with process info,
         *                          composed by xfProcessOrderedContent
         */

        case WOM_PROCESSORDEREDCONTENT: {
            XFolder             *pFolder = (XFolder*)mp1;
            PPROCESSCONTENTINFO pPCI = (PPROCESSCONTENTINFO)mp2;

            #ifdef DEBUG_STARTUP
                _Pmpf(("Entering WOM_PROCESSORDEREDCONTENT..."));
            #endif

            ThreadGlobals.ulWorkerFunc2 = 5000;
            if (xwpsCheckObject(pFolder)) {
                if (pPCI) {
                    if (pPCI->ulObjectNow == 0)
                    {   // first call: initialize structure
                        ThreadGlobals.ulWorkerFunc2 = 5010;
                        pPCI->ulObjectMax = 0;
                        // now count objects
                        for (   pPCI->pObject = _xfQueryOrderedContent(pFolder, NULL, QC_FIRST);
                                (pPCI->pObject);
                                pPCI->pObject = _xfQueryOrderedContent(pFolder, pPCI->pObject, QC_NEXT)
                            )
                        {
                            pPCI->ulObjectMax++;
                        }
                        // get first object
                        pPCI->pObject = _xfQueryOrderedContent(pFolder, NULL, QC_FIRST);
                        #ifdef DEBUG_STARTUP
                            _Pmpf(("  Found %d objects", pPCI->ulObjectMax));
                        #endif
                    } else {
                        // subsequent calls: get next object
                        ThreadGlobals.ulWorkerFunc2 = 5020;
                        pPCI->pObject = _xfQueryOrderedContent(pFolder, pPCI->pObject, QC_NEXT);
                    }

                    ThreadGlobals.ulWorkerFunc2 = 5029;
                    pPCI->ulObjectNow++;
                    ThreadGlobals.ulWorkerFunc2 = 5030;
                    if (pPCI->pObject) {
                        // this is not the last object: start it
                        ThreadGlobals.ulWorkerFunc2 = 5040;
                        if (pPCI->pfnwpCallback) {
                            ThreadGlobals.ulWorkerFunc2 = 5042;
                            if (xwpsCheckObject(pPCI->pObject))
                            {
                                #ifdef DEBUG_STARTUP
                                    _Pmpf(("  Sending XOM_POCCALLBACK"));
                                #endif
                                xthrSendWorkplaceObjectMsg(XOM_POCCALLBACK, (MPARAM)pPCI, MPNULL);
                            }
                        }
                        ThreadGlobals.ulWorkerFunc2 = 5043;
                        pPCI->ulFirstTime = doshGetULongTime();

                        // wait for next object
                        ThreadGlobals.ulWorkerFunc2 = 5044;
                        xthrPostWorkerMsg(WOM_WAITFORPROCESSNEXT,
                           mp1, mp2);
                    }
                    else {
                        // pPCI->pObject == NULL:
                        // no more objects, call callback with NULL, clean up and stop
                        ThreadGlobals.ulWorkerFunc2 = 5050;
                        if (pPCI->pfnwpCallback) {
                            ThreadGlobals.ulWorkerFunc2 = 5052;
                            #ifdef DEBUG_STARTUP
                                _Pmpf(("  Sending XOM_POCCALLBACK, pObject == NULL"));
                            #endif
                            xthrSendWorkplaceObjectMsg(XOM_POCCALLBACK, (MPARAM)pPCI, MPNULL);
                            /* (*(pPCI->pfnwpCallback))(pPCI->ulCallbackParam,
                                (ULONG)NULL,
                                (MPARAM)pPCI->ulObjectNow, (MPARAM)pPCI->ulObjectMax); */
                            ThreadGlobals.ulWorkerFunc2 = 5055;
                        }

                        ThreadGlobals.ulWorkerFunc2 = 5059;
                        free(pPCI);
                    }
                }
            }
            #ifdef DEBUG_STARTUP
                _Pmpf(("  Done with WOM_PROCESSORDEREDCONTENT"));
            #endif

        break; }

        case WOM_WAITFORPROCESSNEXT: {
            // XFolder             *pFolder = (XFolder*)mp1;
            PPROCESSCONTENTINFO pPCI = (PPROCESSCONTENTINFO)mp2;
            BOOL                OKGetNext = FALSE;

            ThreadGlobals.ulWorkerFunc2 = 5100;
            if (pPCI)
            {
                if (!pPCI->fCancelled) {
                    if (pPCI->ulTiming == 0)
                    {   // wait for close mode
                        ThreadGlobals.ulWorkerFunc2 = 5110;
                        if (_wpWaitForClose(pPCI->pObject,
                                pPCI->hwndView, 0, SEM_IMMEDIATE_RETURN, FALSE) == 0)
                        {
                            OKGetNext = TRUE;
                        }
                    } else {
                        // timing mode
                        ThreadGlobals.ulWorkerFunc2 = 5120;
                        if (doshGetULongTime() > ((pPCI->ulFirstTime) + pPCI->ulTiming))
                        {
                            OKGetNext = TRUE;
                        }
                    }

                    ThreadGlobals.ulWorkerFunc2 = 5130;
                    if (OKGetNext)
                    {
                        ThreadGlobals.ulWorkerFunc2 = 5131;
                        xthrPostWorkerMsg(WOM_PROCESSORDEREDCONTENT, mp1, mp2);
                    } else {
                        ThreadGlobals.ulWorkerFunc2 = 5132;
                        xthrPostWorkerMsg(WOM_WAITFORPROCESSNEXT, mp1, mp2);
                        DosSleep(100);
                    }
                }
            }
            ThreadGlobals.ulWorkerFunc2 = 5139;
        break; }

        /*
         * WOM_ADDAWAKEOBJECT:
         *      this is posted by XFldObject for each
         *      object that is awaked by the WPS; we
         *      need to maintain a list of these objects
         *      for XShutdown
         */

        case WOM_ADDAWAKEOBJECT: {
            WPObject              *pObj = (WPObject*)(mp1);
            POBJECTLISTITEM       poli = NULL;

            #ifdef DEBUG_AWAKEOBJECTS
               // _PmpfF(("WT: Adding awake object..."));
            #endif

            // increment global count
            ThreadGlobals.lAwakeObjectsCount++;

            ThreadGlobals.ulWorkerFunc2 = 6000;
            // set the quiet exception handler, because
            // sometimes we get a message for an object too
            // late, i.e. it is not awake any more, and then
            // we'll trap
            TRY_QUIET(excpt2)
            {
                ThreadGlobals.ulWorkerFunc2 = 6010;

                // only save awake abstract and folder objects;
                // if we included all WPFileSystem objects, all
                // of these will be saved at XShutdown, that is,
                // they'll all get .CLASSINFO EAs, which we don't
                // want
                if (    (_somIsA(pObj, _WPAbstract))
                     || (_somIsA(pObj, _WPFolder))
                   )
                {
                    APIRET rc;
                    ThreadGlobals.ulWorkerFunc2 = 6020;
                    if (strcmp(_somGetClassName(pObj), "SmartCenter") == 0)
                        ThreadGlobals.pAwakeWarpCenter = pObj;

                    // get the mutex semphore
                    ThreadGlobals.ulWorkerFunc2 = 6030;
                    fWorkerAwakeObjectsSemOwned =
                            (DosRequestMutexSem(ThreadGlobals.hmtxAwakeObjects, 4000)
                            == NO_ERROR);
                    if (fWorkerAwakeObjectsSemOwned)
                    {
                        ThreadGlobals.ulWorkerFunc2 = 6040;
                        poli = ThreadGlobals.pliAwakeObjectsFirst;
                        while (poli)
                            if (poli->pObj == pObj)
                                break;
                            else
                                poli = poli->pNext;

                        if (poli == NULL)
                        {
                            poli = malloc(sizeof(OBJECTLISTITEM));
                            poli->pObj = pObj;
                            lstAppendItem((PLISTITEM*)&(ThreadGlobals.pliAwakeObjectsFirst),
                                (PLISTITEM*)&(ThreadGlobals.pliAwakeObjectsLast),
                                (PLISTITEM)poli);
                        }

                        ThreadGlobals.ulWorkerFunc2 = 6090;
                    }
                }
            }
            CATCH(excpt2)
            {
                // the thread exception handler puts us here
                // if an exception occured:
                #ifdef DEBUG_AWAKEOBJECTS
                    DosBeep(10000, 10);
                #endif
            } END_CATCH;

            if (fWorkerAwakeObjectsSemOwned)
            {
                DosReleaseMutexSem(ThreadGlobals.hmtxAwakeObjects);
                fWorkerAwakeObjectsSemOwned = FALSE;
            }
        break; }

        /*
         * WOM_REMOVEAWAKEOBJECT:
         *      this is posted by WPObject also, but
         *      when an object goes back to sleep.
         *      Be careful: the object pointer in mp1
         *      does not point to a valid SOM object
         *      any more, because the object has
         *      already been freed in memory; so we
         *      must not call any methods here.
         *      We only use the object pointer
         *      for finding the respective object
         *      in the linked list.
         */

        case WOM_REMOVEAWAKEOBJECT:
        {
            WPObject            *pObj = (WPObject*)(mp1);
            POBJECTLISTITEM     poli = NULL;

            #ifdef DEBUG_AWAKEOBJECTS
                _PmpF(("WT: Removing asleep object."));
            #endif

            // decrement global count
            ThreadGlobals.lAwakeObjectsCount--;

            // get the mutex semaphore
            ThreadGlobals.ulWorkerFunc2 = 7000;
            fWorkerAwakeObjectsSemOwned =
                    (DosRequestMutexSem(ThreadGlobals.hmtxAwakeObjects, 4000)
                    == NO_ERROR);
            if (fWorkerAwakeObjectsSemOwned)
            {
                ThreadGlobals.ulWorkerFunc2 = 7001;
                poli = ThreadGlobals.pliAwakeObjectsFirst;
                ThreadGlobals.ulWorkerFunc2 = 7002;
                while (poli)
                {
                    if (poli->pObj == pObj) {
                        break;
                    }
                    else
                        poli = poli->pNext;
                }

                // remove the object from the list
                if (poli) {
                    ThreadGlobals.ulWorkerFunc2 = 7005;
                    lstRemoveItem((PLISTITEM*)&(ThreadGlobals.pliAwakeObjectsFirst),
                                  (PLISTITEM*)&(ThreadGlobals.pliAwakeObjectsLast),
                                (PLISTITEM)poli);
                }

                DosReleaseMutexSem(ThreadGlobals.hmtxAwakeObjects);
                fWorkerAwakeObjectsSemOwned = FALSE;
            }
        break; }

        /*
         * WOM_REFRESHFOLDERVIEWS:
         *    this one is posted by XFolder's overrides of wpMoveObject,
         *    wpSetTitle or wpRefresh with the calling instance's somSelf
         *    in mp1; we will now update the open frame window titles of both
         *    the caller and its open subfolders with the full folder path
         *    Parameters:
         *    XFolder* mp1  folder to update; if this is NULL, all open
         *                  folders on the system will be updated
         */

        case WOM_REFRESHFOLDERVIEWS: {
            XFolder     *pFolder = NULL,
                        *pCalling = (XFolder*)(mp1);

            ThreadGlobals.ulWorkerFunc2 = 8002;
            for ( pFolder = _wpclsQueryOpenFolders(_WPFolder, NULL, QC_FIRST, FALSE);
                  pFolder;
                  pFolder = _wpclsQueryOpenFolders(_WPFolder, pFolder, QC_NEXT, FALSE))
            {
                ThreadGlobals.ulWorkerFunc2 = 8004;
                if (xwpsCheckObject(pFolder))
                    if (_somIsA(pFolder, _WPFolder))
                    {
                        ThreadGlobals.ulWorkerFunc2 = 8006;
                        if (pCalling == NULL) {
                            ThreadGlobals.ulWorkerFunc2 = 8010;
                            _xfUpdateAllFrameWndTitles(pFolder);
                        } else if (xwpsResidesBelow(pFolder, pCalling)) {
                            ThreadGlobals.ulWorkerFunc2 = 8020;
                            _xfUpdateAllFrameWndTitles(pFolder);
                        }
                    }
            }
        break; }

        /*
         * WOM_UPDATEALLSTATUSBARS:
         *      goes thru all open folder views and updates
         *      status bars if necessary.
         *      Parameters:
         *      ULONG mp1   do-what flag
         *                  1:  show or hide status bars according
         *                      to folder instance and Global settings
         *                  2:  reformat status bars (e.g. because
         *                      fonts have changed)
         */

        case WOM_UPDATEALLSTATUSBARS: {
            #ifdef DEBUG_STATUSBARS
                _Pmpf(( "WT: WOM_UPDATEALLSTATUSBARS" ));
            #endif
            ThreadGlobals.ulWorkerFunc2 = 8500;
            // for each open folder view, call the callback
            // which updates the status bars
            // (fncbUpdateStatusBars in xfldr.c)
            _xfclsForEachOpenView(_XFolder, (ULONG)mp1,
                        (PFNWP)fncbUpdateStatusBars);
        break; }

        /*
         * WM_INVALIDATEORDEREDCONTENT:
         *      this one is posted when the .ICONPOS data
         *      has changed for a folder;
         *      again, the calling instance's somSelf is in mp1;
         *      we will now delete our iconpos list, so it can
         *      be rebuilt when needed
         */

        case WOM_INVALIDATEORDEREDCONTENT: {

            // XFolderData *somThis;

            ThreadGlobals.ulWorkerFunc2 = 9000;
            if (!xwpsCheckObject((WPObject*)(mp1)))
                break;

            ThreadGlobals.ulWorkerFunc2 = 9001;
            if (_somIsA((WPObject*)(mp1), _XFolder))
                _xfInvalidateOrderedContent((XFolder*)(mp1));
        break; }

        /*
         * WOM_DELETEICONPOSEA:
         *
         */

        case WOM_DELETEICONPOSEA: {
            /* this msg is posted when the .ICONPOS EAs are
               do be deleted; we better do this in the
               background thread */
            EABINDING   eab;
            XFolder     *pFolder = (XFolder*)mp1;
            CHAR        szPath[CCHMAXPATH];

            ThreadGlobals.ulWorkerFunc2 = 9500;
            if (!xwpsCheckObject(pFolder))
                break;

            _wpQueryFilename(pFolder, szPath, TRUE); // fully qualfd.
            eab.bFlags = 0;
            eab.pszName = ".ICONPOS";
            eab.bNameLength = strlen(eab.pszName);
            eab.pszValue = "";
            eab.usValueLength = 0;
            eaPathWriteOne(szPath, &eab);
        break; }


        /*
         * WOM_DELETEFOLDERPOS:
         *      (new with V0.82) this is posted by
         *      XFolder::wpFree (i.e. when a folder
         *      is deleted); we will now search the
         *      OS2.INI file for folderpos entries
         *      for the deleted folder and remove
         *      them. This is only posted if the
         *      corresponding setting in "Workplace
         *      Shell" is on.
         *      Parameters: mp1 contains the
         *          (hex) five-digit handle, which
         *          should always be 0x3xxxx.
         */

        case WOM_DELETEFOLDERPOS: {
            // according to Henk Kelder:
            // for each open folder the WPS creates an INI entry, which
            // key is the decimal value of the HOBJECT, followed by
            // some wicked "@" codes, which probably stand for the
            // various folder views. Since I don't know the codes,
            // I'm getting the keys list for PM_Workplace:FolderPos.

            PSZ pszFolderPosKeys = prfhQueryKeysForApp(HINI_USER,
                        WPINIAPP_FOLDERPOS);

            if (pszFolderPosKeys)
            {
                PSZ     pKey2 = pszFolderPosKeys;
                CHAR    szComp[20];
                ULONG   cbComp;
                sprintf(szComp, "%d@", (ULONG)mp1);
                cbComp = strlen(szComp);

                #ifdef DEBUG_CNRCONTENT
                _Pmpf(("Checking for folderpos %s", szComp));
                #endif

                // now walk thru all the keys in the folderpos
                // application and check if it's one for our
                // folder; if so, delete it
                while (*pKey2 != 0)
                {
                    if (memcmp(szComp, pKey2, cbComp) == 0) {
                        PrfWriteProfileData(HINI_USER, WPINIAPP_FOLDERPOS, pKey2,
                            NULL, 0);
                        #ifdef DEBUG_CNRCONTENT
                            _Pmpf(("  Deleted %s", pKey2));
                        #endif
                    }

                    pKey2 += strlen(pKey2)+1;
                }
                free(pszFolderPosKeys);
            }
        break; }

        /*
         * WOM_TREEVIEWAUTOSCROLL:
         *     this msg is posted by the fnwpSubclassedFolderFrame
         *     (subclassed folder windows) after the "plus" sign has
         *     been clicked on (WM_CONTROL for containers with
         *     CN_EXPANDTREE notification). Parameters:
         *          HWND mp1:    frame wnd handle
         *          PMINIRECORDCORE mp2:
         *                       the expanded minirecordcore
         */

        case WOM_TREEVIEWAUTOSCROLL: {
            WPObject    *pFolder = OBJECT_FROM_PREC((PMINIRECORDCORE)mp2);
            ThreadGlobals.ulWorkerFunc2 = 10000;

            if (xwpsCheckObject(pFolder))
            {
                ThreadGlobals.ulWorkerFunc2 = 10010;
                while (_somIsA(pFolder, _WPShadow))
                    pFolder = _wpQueryShadowedObject(pFolder, TRUE);

                ThreadGlobals.ulWorkerFunc2 = 10020;
                if (!_somIsA(pFolder, _WPFolder)) // check only folders, avoid disks
                    break;

                // now check if the folder whose "plus" sign has been
                // clicked on is already populated: if so, the WPS seems
                // to insert the objects directly (i.e. in the Workplace
                // thread), if not, the objects are inserted by some
                // background populate thread, which we have no control
                // over...
                ThreadGlobals.ulWorkerFunc2 = 10030;
                if ( (_wpQueryFldrFlags(pFolder) & FOI_POPULATEDWITHFOLDERS) == 0)
                {
                    // NOT fully populated: sleep a while, then post
                    // the same msg again, until the "populated" folder
                    // flag has been set
                    DosSleep(100);
                    ThreadGlobals.ulWorkerFunc2 = 10040;
                    xthrPostWorkerMsg(WOM_TREEVIEWAUTOSCROLL, mp1, mp2);
                }
                else {
                    // otherwise: scroll the tree view properly
                    HWND                hwndCnr = WinWindowFromID((HWND)mp1, 0x8008);
                    PMINIRECORDCORE     preccLastChild;
                    ThreadGlobals.ulWorkerFunc2 = 10050;
                    preccLastChild = WinSendMsg(hwndCnr, CM_QUERYRECORD,
                        mp2,   // PMINIRECORDCORE
                        MPFROM2SHORT(CMA_LASTCHILD, CMA_ITEMORDER));
                    winhCnrScrollToRecord(hwndCnr,
                            (PRECORDCORE)preccLastChild,
                            CMA_TEXT,
                            TRUE);
                }
                ThreadGlobals.ulWorkerFunc2 = 10090;
            }
        break; }

        default:
            mrc = WinDefWindowProc(hwndObject, msg, mp1, mp2);

    } // end switch

    ThreadGlobals.ulWorkerFunc2 = 99990;
    ThreadGlobals.ulWorkerFunc2 = 99991;

    return (mrc);
}

/*
 *@@ fntWorkerThread:
 *          this is the thread function for the XFolder
 *          "Worker" (= background) thread, to which
 *          messages for tasks are passed which are too
 *          time-consuming to be processed on the
 *          Workplace thread; it creates an object window
 *          and stores its handle in hwndWorkerObject.
 *          This thread is created by xthrInitializeThreads.
 */

void _Optlink fntWorkerThread(PVOID ptiMyself)
{
    QMSG                  qmsg;
    PSZ                   pszErrMsg;

    begin:

    TRY_LOUD(excpt1)   // install "loud" exception handler (except.h)
    {
        ThreadGlobals.ulWorkerFunc2 = 599;

        // we will now create the mutex semaphore for access to
        // the linked list of awake objects; this is used in
        // wpInitData and wpUnInitData */
        DosCreateMutexSem("\\sem32\\AwakeObjectsList",
                            &(ThreadGlobals.hmtxAwakeObjects),
                            0, FALSE);  // unnamed, unowned

        ThreadGlobals.ulWorkerFunc2 = 1;
        if (habWorkerThread = WinInitialize(0))
        {
            if (hmqWorkerThread = WinCreateMsgQueue(habWorkerThread, 4000))
            {
                CHAR szWorkerClass[] = "XFolderWorkerObject";

                ThreadGlobals.ulWorkerFunc2 = 2;
                WinCancelShutdown(hmqWorkerThread, TRUE);

                ThreadGlobals.WorkerThreadHighPriority = FALSE;

                WinRegisterClass(habWorkerThread,
                                 szWorkerClass,    // class name
                                 (PFNWP)fnwpWorkerObject,    // Window procedure
                                 0,                  // class style
                                 0);                 // extra window words

                // create Worker object window
                ThreadGlobals.hwndWorkerObject = WinCreateWindow(
                                    HWND_OBJECT,
                                    szWorkerClass,
                                    (PSZ)"",     // title
                                    0,           // style
                                    0,0,0,0,     // position
                                    0,           // owner
                                    HWND_BOTTOM, // z-order
                                    ID_WORKEROBJECT, // window id
                                    NULL,        // create params
                                    NULL);       // pres params

                if (!ThreadGlobals.hwndWorkerObject)
                    DebugBox("XFolder: Error", "XFolder failed to create the Worker thread object window.");

                // set ourselved to idle-time priority; this will
                //   be raised to regular when the msg queue becomes congested
                ThreadGlobals.WorkerThreadHighPriority = TRUE;
                        // otherwise the following func won't work right
                xthrRaiseWorkerThreadPriority(FALSE);

                // now enter the message loop
                while (WinGetMsg(habWorkerThread, &qmsg, NULLHANDLE, 0, 0))
                {
                    ThreadGlobals.ulWorkerFunc2 = 9;
                    if (ulWorkerMsgCount > 0)
                        ulWorkerMsgCount--;

                    if (ulWorkerMsgCount < 10)
                        xthrRaiseWorkerThreadPriority(FALSE);

                    // dispatch the queue msg
                    WinDispatchMsg(habWorkerThread, &qmsg);
                    ThreadGlobals.ulWorkerFunc2 = 99992;
                }
                // loop until WM_QUIT

                ThreadGlobals.ulWorkerFunc2 = 99993;
                WinDestroyWindow(ThreadGlobals.hwndWorkerObject);
                ThreadGlobals.hwndWorkerObject = NULLHANDLE;
                WinDestroyMsgQueue(hmqWorkerThread);
                hmqWorkerThread = NULLHANDLE;
                ThreadGlobals.ulWorkerFunc2 = 99994;
            }
            WinTerminate(habWorkerThread);
            ThreadGlobals.ulWorkerFunc2 = 99995;
        }
    }
    CATCH(excpt1) {
        HWND hwndDesktop;

        // the thread exception handler puts us here if an exception occured:
        ThreadGlobals.ulWorkerFunc2 = 510;

        // release mutex semaphores owned by the Worker thread
        if (fWorkerAwakeObjectsSemOwned) {
            DosReleaseMutexSem(ThreadGlobals.hmtxAwakeObjects);
            fWorkerAwakeObjectsSemOwned = FALSE;
        }

        // clean up
        ThreadGlobals.ulWorkerFunc2 = 515;
        WinDestroyWindow(ThreadGlobals.hwndWorkerObject);
        WinDestroyMsgQueue(hmqWorkerThread);
        WinTerminate(habWorkerThread);

        hwndDesktop = _wpclsQueryActiveDesktopHWND(_WPDesktop);
        if (    (hwndDesktop)
             && (pszErrMsg == NULL)
           )
        {
            // only report the first error, or otherwise we will
            // jam the system with msg boxes
            ThreadGlobals.ulWorkerFunc2 = 516;
            pszErrMsg = malloc(1000);
            if (pszErrMsg) {
                strcpy(pszErrMsg, "An error occured in the XFolder Worker thread. "
                        "Since the error is probably not serious, "
                        "the Worker thread will continue to run. However, if errors "
                        "like these persist, you might want to disable the "
                        "Worker thread in the XFolder Global Settings, "
                        "contact XFolder's author, and send him the file XFLDTRAP.LOG, "
                        "which you will find in the root directory of your boot drive. ");
                xthrPostWorkplaceObjectMsg(XOM_EXCEPTIONCAUGHT, (MPARAM)pszErrMsg, MPNULL);
            }
        }

        // "restart" thread
        goto begin;
    } END_CATCH;

    ThreadGlobals.ulWorkerFunc2 = 99996;
    DosCloseMutexSem(ThreadGlobals.hmtxAwakeObjects);

    ThreadGlobals.ulWorkerFunc2 = 99997;
    thrGoodbye((PTHREADINFO)ptiMyself);
}

ULONG   ulVolumeTemp = 0;
SHAPEFRAME sb = {0};


/*
 *@@ fnwpQuickObject:
 *      wnd proc for Quick thread object window
 *      (see fntQuickThread below)
 */

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

    ThreadGlobals.ulQuickFunc2 = 1100;
    switch (msg) {

        /*
         * WM_CREATE:
         *      show XFolder logo at bootup
         */

        case WM_CREATE: {
            PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
            // these are already initialized by xfobjM_wpclsInitData

            if (doshQueryShiftState())
                // shift pressed --> skip logo (emergency)
                DosBeep(1000, 200);
            else
                // create the shaped window for the
                // XFolder logo (shapewin.c)
                if (pGlobalSettings->ShowXFolderAnim)
                        // logo allowed?
                {
                    CHAR szBitmapFile[CCHMAXPATH];

                    // load bitmap from file
                    cmnQueryXFolderBasePath(szBitmapFile);

                    strcat(szBitmapFile,
                            "\\bootlogo\\bootlogo.bmp");

                    if (shpLoadBitmap(habQuickThread,
                                        szBitmapFile,
                                        0, 0, /* modQueryHandle(),
                                        ID_XFLDRBIGLOGO, */
                                        &sb))
                    {
                        // blow up bitmap
                        /* anmBlowUpBitmap(sb.hps, sb.hbm,
                                        1000);  // total animation time
                           */

                        // create shape (transparent) windows
                        if (shpCreateWindows(habQuickThread, &sb))
                        {
                            // now center the logo on the screen
                            SWP     swpScreen;
                            SHORT   x, y;
                            WinQueryWindowPos(HWND_DESKTOP, &swpScreen);
                            x = (swpScreen.cx - sb.bmi.cx) / 2;
                            y = (swpScreen.cy - sb.bmi.cy) / 2;
                            WinSetWindowPos(sb.hwndShapeFrame, NULLHANDLE,
                                    x, y,
                                    sb.bmi.cx, sb.bmi.cy,
                                    (SWP_MOVE | SWP_SIZE | SWP_HIDE));
                            WinSetWindowPos(sb.hwndShape, NULLHANDLE,
                                    x, y,
                                    sb.bmi.cx, sb.bmi.cy,
                                    (SWP_MOVE | SWP_SIZE | SWP_SHOW));
                        } // end if (hbm)

                        // WinReleasePS(hps);
                    } // end if (hps)
                } // end if (pGlobalSettings->ShowXFolderAnim)

            mrc = WinDefWindowProc(hwndObject, msg, mp1, mp2);
        break; }

        /*
         * QM_DESTROYLOGO:
         *
         */

        case QM_DESTROYLOGO: {
            PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
            if (pGlobalSettings->ShowXFolderAnim)
            {
                shpFreeBitmap(&sb);
                WinDestroyWindow(sb.hwndShapeFrame) ;
                WinDestroyWindow(sb.hwndShape);
            }
        break; }

        /*
         * QM_BOOTUPSTATUS:
         *      posted by XFldObject::wpclsInitData every
         *      time a WPS class gets initialized and
         *      "Report class initialization" on the "WPS
         *      Classes" page is on. We will then display
         *      a little window for a few seconds.
         *      Parameters: mp1 points to the SOM class
         *      object being initalized; if NULL, then
         *      the bootup status wnd will be destroyed.
         */

        case QM_BOOTUPSTATUS: {
            if (hwndBootupStatus == NULLHANDLE) {
                // window currently not visible:
                // create it
                strcpy(szBootupStatus, "Loaded %s");
                hwndBootupStatus = WinLoadDlg(HWND_DESKTOP, HWND_DESKTOP,
                            fnwpGenericStatus,
                            NLS_MODULE, ID_XFD_BOOTUPSTATUS,
                            NULL);
                WinSetWindowPos(hwndBootupStatus,
                        HWND_TOP,
                        0, 0, 0, 0,
                        SWP_SHOW); // show only, do not activate;
                            // we don't want the window to get the
                            // focus, because this would close
                            // open menus and such
            }

            if (hwndBootupStatus)
                // window still visible or created anew:
                if (mp1 == NULL)
                    WinDestroyWindow(hwndBootupStatus);
                else {
                    CHAR szTemp[2000];
                    WPObject* pObj = (WPObject*)mp1;

                    // get class title (e.g. "WPObject")
                    PSZ pszClassTitle = _somGetName(pObj);
                    if (pszClassTitle)
                        sprintf(szTemp, szBootupStatus, pszClassTitle);
                    else sprintf(szTemp, szBootupStatus, "???");

                    WinSetDlgItemText(hwndBootupStatus, ID_XFDI_BOOTUPSTATUSTEXT,
                            szTemp);

                    // show window for 2 secs
                    WinStartTimer(habWorkerThread, hwndObject, 1, 2000);
                }
        break; }

        /*
         * WM_TIMER:
         *      destroy bootup status wnd
         */

        case WM_TIMER: {
            switch ((USHORT)mp1)  {// timer id

                // timer 1: bootup status wnd
                case 1:
                    if (hwndBootupStatus) {
                        WinDestroyWindow(hwndBootupStatus);
                        hwndBootupStatus = NULLHANDLE;
                    }
                    WinStopTimer(habWorkerThread, hwndObject, 1);
                break;
            }
        break; }

        /*
         * QM_PLAYSYSTEMSOUND:
         *      plays system sound specified in MMPM.INI.
         *      This is posted by xthrPlaySystemSound.
         *      (USHORT)mp1 must be the MMPM.INI index (see
         *      cmnQuerySystemSound in common.c for a list).
         */

        case QM_PLAYSYSTEMSOUND: {
            CHAR    szDescr[CCHMAXPATH];
            ULONG   ulVolume;
            // allocate mem for sound file; this will
            // be freed in QM_PLAYSOUND below
            PSZ     pszFile = malloc(CCHMAXPATH);

            #ifdef DEBUG_SOUNDS
                _Pmpf(( "QM_PLAYSYSTEMSOUND index %d", mp1));
            #endif
            ThreadGlobals.ulQuickFunc2 = 1200;

            // get system sound from MMPM.INI
            if (cmnQuerySystemSound((USHORT)mp1,
                            szDescr,
                            pszFile,
                            &ulVolume))
            {
                // OK, sound file found in MMPM.INI:
                #ifdef DEBUG_SOUNDS
                    _Pmpf(( "QM: posting Sound %d == %s, %s", mp1, szDescr, pszFile ));
                #endif

                // now let's check for whether that sound file
                // really exists
                if (access(pszFile, 0) == 0)
                {
                    WinPostMsg(hwndObject,
                                QM_PLAYSOUND,
                                (MPARAM)pszFile, (MPARAM)ulVolume);
                    break;
                }
            }

            // any error: do nothing
            free(pszFile);
        break; }

        /*
         * QM_PLAYSOUND:
         *      plays the sound file specified in
         *      (PSZ)mp1; this PSZ is assumed to have
         *      been allocated using malloc() and will
         *      be freed afterwards.
         *      (ULONG)mp2 must be the volume (0-100).
         *      This message is only posted by
         *      QM_PLAYSYSTEMSOUND (above) if a system
         *      sound was queried successfully.
         *      To play sounds in the Quick thread, we use
         *      SOUND.DLL, which should have been loaded
         *      at WPS startup. (See xthrInitializeThreads.
         *      If not, we don't get this message in the
         *      first place.)
         *      Playing sounds is a three-step process:
         *      1)  We call sndOpenSound in SOUND.DLL first
         *          to open a waveform device for this sound
         *          file as a _shareable_ device and then
         *          stop for the moment.
         *      2)  If this device is accessible, MMPM/2 then
         *          posts us MM_MCIPASSDEVICE (below) so we can
         *          play the sound.
         *      3)  We close the device if we're either losing
         *          it (because another app needs it -- that's
         *          MM_MCIPASSDEVICE with MCI_LOSING_USE set)
         *          or if MMPM/2 is done with our sound (that's
         *          MM_MCINOTIFY below).
         */

        case QM_PLAYSOUND: {
            if (mp1)
            {
                if (psndOpenSound) // func ptr into SOUND.DLL
                    (*psndOpenSound)(hwndObject,
                                     &ThreadGlobals.usDeviceID,
                                     (PSZ)mp1);
                            // this will post MM_MCIPASSDEVICE
                            // if the device is available
                free((PSZ)mp1);
                ulVolumeTemp = (ULONG)mp2;
            }
        break; }

        /*
         * MM_MCIPASSDEVICE:
         *      MMPM/2 posts this msg for shareable devices
         *      to allow multimedia applications to behave
         *      politely when several applications use the
         *      same device. This is posted to us in two cases:
         *      1)  opening the device above was successful
         *          and the device is available (that is, no
         *          other application needs exclusive access
         *          to that device); in this case, mp2 has the
         *          MCI_GAINING_USE flag set, and we can call
         *          sndPlaySound in SOUND.DLL to actually
         *          play the sound.
         *          The device is _not_ available, for example,
         *          when a Win-OS/2 session is running which
         *          uses sounds.
         *      2)  While we are playing, another application
         *          is trying to get access to the device; in
         *          this case, mp2 has the MCI_LOSING_USE flag
         *          set, and we call sndStopSound to stop
         *          playing our sound.
         */

        #define MM_MCINOTIFY                        0x0500
        #define MM_MCIPASSDEVICE                    0x0501
        #define MCI_LOSING_USE                      0x00000001L
        #define MCI_GAINING_USE                     0x00000002L
        #define MCI_NOTIFY_SUCCESSFUL               0x0000

        case MM_MCIPASSDEVICE: {

            BOOL fGainingUse = (SHORT1FROMMP(mp2) == MCI_GAINING_USE);

            #ifdef DEBUG_SOUNDS
                _Pmpf(( "MM_MCIPASSDEVICE: mp1 = 0x%lX, mp2 = 0x%lX", mp1, mp2 ));
                _Pmpf(( "    %s use", (fGainingUse) ? "Gaining" : "Losing" ));
            #endif

            if (fGainingUse) {
                // we're gaining the device (1): play sound
                if (psndPlaySound)  // func ptr into SOUND.DLL
                    (*psndPlaySound)(hwndObject,
                                     &ThreadGlobals.usDeviceID,
                                     ulVolumeTemp);
            } else
                // we're losing the device (2): stop sound
                if (psndStopSound)  // func ptr into SOUND.DLL
                    (*psndStopSound)(&ThreadGlobals.usDeviceID);
        break; }

        /*
         * MM_MCINOTIFY:
         *      this is the general notification msg of MMPM/2.
         *      We need this message to know when MMPM/2 is done
         *      playing our sound; we will then close the device.
         */

        case MM_MCINOTIFY:
        {
            #ifdef DEBUG_SOUNDS
                _Pmpf(( "MM_MCINOTIFY: mp1 = 0x%lX, mp2 = 0x%lX", mp1, mp2 ));
            #endif
            ThreadGlobals.ulQuickFunc2 = 1400;

            if (    (SHORT1FROMMP(mp2) == ThreadGlobals.usDeviceID)
                 && (SHORT1FROMMP(mp1) == MCI_NOTIFY_SUCCESSFUL)
               )
            {
                if (psndStopSound)  // func ptr into SOUND.DLL
                    (*psndStopSound)(&ThreadGlobals.usDeviceID);
            }
        break; }

        default:
            ThreadGlobals.ulQuickFunc2 = 1500;
            mrc = WinDefWindowProc(hwndObject, msg, mp1, mp2);
    }

    ThreadGlobals.ulQuickFunc2 = 1600;
    return (mrc);
}

/*
 *@@ fntQuickThread:
 *          this is the thread function for the XFolder
 *          "Quick" thread, which is responsible for
 *          showing that bootup logo and displaying
 *          the notification window when classes get initialized.
 *          Also this plays the new XFolder system sounds.
 *          As opposed to the "Worker" thread, this thread runs
 *          with regular priority.
 *          This thread is also created from xthrInitializeThreads.
 */

void _Optlink fntQuickThread(PVOID ptiMyself)
{
    QMSG                  qmsg;
    PSZ                   pszErrMsg = NULL;

    TRY_LOUD(excpt1)   // install "loud" exception handler (except.h)
    {
        ThreadGlobals.ulQuickFunc2 = 1;
        if (habQuickThread = WinInitialize(0))
        {
            if (hmqQuickThread = WinCreateMsgQueue(habQuickThread, 3000))
            {
                CHAR szQuickClass[] = "XFolderQuickObject";

                ThreadGlobals.ulQuickFunc2 = 2;
                WinCancelShutdown(hmqQuickThread, TRUE);

                WinRegisterClass(habQuickThread,
                                 szQuickClass,    // class name
                                 (PFNWP)fnwpQuickObject,    // Window procedure
                                 0,                  // class style
                                 0);                 // extra window words

                // set ourselves to higher regular priority
                DosSetPriority(PRTYS_THREAD,
                               PRTYC_REGULAR,
                               +31, // priority delta
                               0);

                // create object window
                ThreadGlobals.hwndQuickObject = WinCreateWindow(
                                    HWND_OBJECT,
                                    szQuickClass,
                                    (PSZ)"",     // title
                                    0,           // style
                                    0,0,0,0,     // position
                                    0,           // owner
                                    HWND_BOTTOM, // z-order
                                    ID_WORKEROBJECT,  // window id
                                    NULL,        // create params
                                    NULL);       // pres params

                if (!ThreadGlobals.hwndQuickObject)
                    DebugBox("XFolder: Error", "XFolder failed to create the Quick thread object window.");

                // now enter the message loop
                while (WinGetMsg(habQuickThread, &qmsg, NULLHANDLE, 0, 0)) {
                    ThreadGlobals.ulQuickFunc2 = 9;
                    WinDispatchMsg(habQuickThread, &qmsg);
                }
                // loop until WM_QUIT

                ThreadGlobals.ulQuickFunc2 = 99993;
                WinDestroyWindow(ThreadGlobals.hwndQuickObject);
                ThreadGlobals.hwndQuickObject = NULLHANDLE;
                WinDestroyMsgQueue(hmqQuickThread);
                hmqQuickThread = NULLHANDLE;
                ThreadGlobals.ulQuickFunc2 = 99994;
            }
            WinTerminate(habQuickThread);
            ThreadGlobals.ulQuickFunc2 = 99995;
        }
    }
    CATCH(excpt1) {
        HWND hwndDesktop;

        // the thread exception handler puts us here if an exception occured:
        ThreadGlobals.ulQuickFunc2 = 510;

        // clean up
        ThreadGlobals.ulQuickFunc2 = 515;
        WinDestroyWindow(ThreadGlobals.hwndQuickObject);
        ThreadGlobals.hwndQuickObject = NULLHANDLE;
        WinDestroyMsgQueue(hmqQuickThread);
        WinTerminate(habQuickThread);

        hwndDesktop = _wpclsQueryActiveDesktopHWND(_WPDesktop);
        if (    (hwndDesktop)
             && (pszErrMsg == NULL)
           )
        {
            // only report the first error, or otherwise we will
            // jam the system with msg boxes
            ThreadGlobals.ulQuickFunc2 = 516;
            pszErrMsg = malloc(1000);
            if (pszErrMsg) {
                strcpy(pszErrMsg, "An error occured in the XFolder Quick thread. "
                        "\n\nThe additional XFolder system sounds will be disabled for the "
                        "rest of this Workplace Shell session. You will need to restart "
                        "the WPS in order to re-enable them. "
                        "\n\nIf errors like these persist, you might want to disable the "
                        "additional XFolder system sounds again. For doing this, execute "
                        "SOUNDOFF.CMD in the BIN subdirectory of the XFolder installation "
                        "directory. "
                        "\n\nYou will find additional debug information "
                        "in the XFLDTRAP.LOG file in the root directory of your boot drive. ");
                xthrPostWorkplaceObjectMsg(XOM_EXCEPTIONCAUGHT, (MPARAM)pszErrMsg, MPNULL);
            }
        }

        // disable sounds
        ThreadGlobals.ulMMPM2Working = MMSTAT_CRASHED;
        // get out of here
    } END_CATCH;

    ThreadGlobals.ulQuickFunc2 = 99997;
    thrGoodbye((PTHREADINFO)ptiMyself);
    // _endthread();
}

/*
 *@@ xthrInitializeThreads:
 *      this starts both the Worker and Quick threads.
 *      Also, we attempt to load SOUND.DLL and resolve
 *      a few exported functions from there.
 *      This is called from M_XFldObject::wpclsInitData
 *      at WPS startup.
 */

VOID xthrInitializeThreads(VOID)
{
    CHAR szSoundDLL[CCHMAXPATH] = "";
    CHAR szError[300] = "";

    if (ThreadGlobals.hwndWorkplaceObject == NULLHANDLE)
    {
        // store WPS startup time
        DosGetDateTime(&ThreadGlobals.StartupDateTime);

        // create main object window
        WinRegisterClass(WinQueryAnchorBlock(HWND_DESKTOP),
                         WNDCLASS_XFLDOBJECT,    // class name
                         (PFNWP)fnwpXFolderObject,    // Window procedure
                         0,                  // class style
                         0);                 // extra window words
        ThreadGlobals.hwndWorkplaceObject = WinCreateWindow(
                            HWND_OBJECT,
                            WNDCLASS_XFLDOBJECT, // class name
                            (PSZ)"",     // title
                            0,           // style
                            0,0,0,0,     // position
                            0,           // owner
                            HWND_BOTTOM, // z-order
                            ID_XFOLDEROBJECT, // window id
                            NULL,        // create params
                            NULL);       // pres params

        if (ThreadGlobals.hwndWorkplaceObject == NULLHANDLE)
            DebugBox("XFolder: Error",
                    "XFolder failed to create the XFolder Workplace object window.");
    }

    if (thrQueryID(ThreadGlobals.ptiWorkerThread) == NULLHANDLE)
    {
        PGLOBALSETTINGS pGlobalSettings = cmnQueryGlobalSettings();
        PTIB        ptib;
        PPIB        ppib;
        APIRET      arc;

        // store the thread ID of the calling thread;
        // this should always be 1
        if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR)
            if (ptib)
                if (ptib->tib_ptib2)
                    ThreadGlobals.tidWorkplaceThread = ptib->tib_ptib2->tib2_ultid;

        // attempt to load SOUND.DLL
        if (cmnQueryXFolderBasePath(szSoundDLL))
        {
            // path found: append helpfile
            strcat(szSoundDLL,
                    "\\bin\\sound.dll");
            #ifdef DEBUG_SOUNDS
                _Pmpf(("Loading %s", szSoundDLL));
            #endif

            if (access(szSoundDLL, 0) == 0)
            {
                arc = DosLoadModule(szError, sizeof(szError),
                        szSoundDLL,
                        &hmodSoundDLL);
                #ifdef DEBUG_SOUNDS
                    _Pmpf(("  arc: %d", arc));
                #endif

                if (arc != NO_ERROR) {
                    // CHAR szTemp[1000];
                    #ifdef DEBUG_SOUNDS
                        _Pmpf(("SOUND.DLL could not be loaded. DosLoadModule rc: %d, "
                            "message: %d.", arc, szError));
                    #endif
                    // DebugBox("XFolder: Error",
                            // szTemp);
                    ThreadGlobals.ulMMPM2Working = MMSTAT_SOUNDLLNOTLOADED;
                } else {
                    ThreadGlobals.ulMMPM2Working = MMSTAT_WORKING;
                    // SOUND.DLL loaded successfully:
                    // resolve imports. We have to do this
                    // dynamically, because SOUND.DLL loading
                    // will fail if MMPM/2 is not installed,
                    // and if we do static imports, loading
                    // of XFLDR.DLL would fail also.
                    arc = DosQueryProcAddr(hmodSoundDLL,
                                0,
                                "sndOpenSound",
                                (PFN*)&psndOpenSound);
                    #ifdef DEBUG_SOUNDS
                        _Pmpf(("  psndOpenSound: arc %d, 0x%X",
                                arc, psndOpenSound));
                    #endif
                    if (arc != NO_ERROR) {
                        psndOpenSound = NULL;
                        ThreadGlobals.ulMMPM2Working = MMSTAT_SOUNDLLFUNCERROR;
                    }

                    arc = DosQueryProcAddr(hmodSoundDLL,
                                0,
                                "sndPlaySound",
                                (PFN*)&psndPlaySound);
                    #ifdef DEBUG_SOUNDS
                        _Pmpf(("  psndPlaySound: arc %d, 0x%X",
                                arc, psndPlaySound));
                    #endif
                    if (arc != NO_ERROR) {
                        psndPlaySound = NULL;
                        ThreadGlobals.ulMMPM2Working = MMSTAT_SOUNDLLFUNCERROR;
                    }

                    arc = DosQueryProcAddr(hmodSoundDLL,
                                0,
                                "sndStopSound",
                                (PFN*)&psndStopSound);
                    #ifdef DEBUG_SOUNDS
                        _Pmpf(("  psndStopSound: arc %d, 0x%X",
                                arc, psndStopSound));
                    #endif
                    if (arc != NO_ERROR) {
                        psndStopSound = NULL;
                        ThreadGlobals.ulMMPM2Working = MMSTAT_SOUNDLLFUNCERROR;
                    }
                }
            } else
                ThreadGlobals.ulMMPM2Working = MMSTAT_SOUNDLLNOTFOUND;
        }

        if (pGlobalSettings->NoWorkerThread == 0)
        {
            ulWorkerMsgCount = 0;
            thrCreate(&(ThreadGlobals.ptiWorkerThread),
                      fntWorkerThread,
                      0);
            thrCreate(&(ThreadGlobals.ptiQuickThread),
                      fntQuickThread,
                      0);
        }
    }
}


