/* File: progcm.c */
/**************************************************************************/
/*	Install: Program Manager commands.
/*	Uses DDE to communicate with ProgMan
/*	Can create groups, delete groups, add items to groups
/*	Originally written 3/9/89 by toddla (the stuff that looks terrible)
/*	Munged greatly for STUFF 4/15/91 by chrispi (the stuff that doesn't work)
/**************************************************************************/

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <cmnds.h>
#include <dde.h>
#include "install.h"
#include "uilstf.h"

#define BIG_ENUF 1024
_dt_system(Install)
_dt_subsystem(ProgMan Operations)

HANDLE
ExecuteApplication(
    LPSTR lpApp,
    WORD  nCmdShow
    );

HWND hwndFrame;
HWND hwndProgressGizmo;

CHAR	szProgMan[] = "PROGMAN";
HWND	hwndDde     = NULL;        // dummy window to handle DDE messages
HWND	hwndProgMan = NULL;        // global handle of progman window
BOOL	fInitiate   = fFalse;      // are we initializing?
BOOL    fAck        = fFalse;
BOOL    fProgManExeced     = fFalse;
HANDLE  hInstCur    = NULL;


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private BOOL APIENTRY FDdeTerminate(VOID)
{
	PreCondition(hwndProgMan != NULL, fFalse);
    PreCondition(hwndDde     != NULL, fFalse);

    SetForegroundWindow(hwndFrame);
    UpdateWindow(hwndFrame);
    MPostWM_DDE_TERMINATE( hwndProgMan, hwndDde );
	hwndProgMan = NULL;

	return(fTrue);
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private LONG APIENTRY WndProcDde(HWND hwnd, UINT uiMessage, WPARAM wParam,
		LONG lParam)
{
	AssertDataSeg();

    switch (uiMessage) {

    case WM_DDE_TERMINATE:

        if(hwndProgMan == NULL) {
            DestroyWindow(hwnd);
            hwndDde = NULL;
        }
        else {
            EvalAssert(FDdeTerminate());
        }

        DDEFREE( uiMessage, lParam );
        return(0L);

    case WM_DDE_ACK:

        if (fInitiate) {

            ATOM aApp   = LOWORD(lParam);
            ATOM aTopic = HIWORD(lParam);

            hwndProgMan = (HWND)wParam;     //conversation established 1632
            GlobalDeleteAtom (aApp);
            GlobalDeleteAtom (aTopic);
        }

        else {

            WORD   wStatus   = GET_WM_DDE_EXECACK_STATUS(wParam, lParam);
            HANDLE hCommands = GET_WM_DDE_EXECACK_HDATA(wParam, lParam);

            fAck = ((DDEACK *)(&wStatus))->fAck;
            GlobalFree(hCommands);

            DDEFREE( uiMessage, lParam );

        }

        return(0L);

    default:

        break;

    }

	return(DefWindowProc(hwnd, uiMessage, wParam, lParam));
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private BOOL APIENTRY FDdeInit(HANDLE hInst)
{

    if (hInst == NULL) {

        /* try to re-init with hInst from last FDdeInit call */

        if (hInstCur == NULL) {
            return(fFalse);
        }

		hInst = hInstCur;
    }
    else {

        hInstCur = hInst;

    }

    if (hwndDde == NULL) {

		static CHP szClassName[] = "ddeClass";
		WNDCLASS rClass;

		Assert(hwndProgMan == NULL);

        if (!GetClassInfo(hInst, szClassName, &rClass)) {
			rClass.hCursor       = NULL;
			rClass.hIcon         = NULL;
			rClass.lpszMenuName  = NULL;
			rClass.lpszClassName = szClassName;
			rClass.hbrBackground = NULL;
			rClass.hInstance     = hInst;
			rClass.style         = 0;
			rClass.lpfnWndProc   = WndProcDde;
			rClass.cbClsExtra    = 0;
			rClass.cbWndExtra    = 0;

            if (!RegisterClass(&rClass)) {
                return(fFalse);
            }

        }

        hwndDde = CreateWindow(
                       szClassName,
                       NULL,
                       0L,
                       0, 0, 0, 0,
                       (HWND)NULL,
                       (HMENU)NULL,
                       (HANDLE)hInst,
                       (LPSTR)NULL
                       );
    }

	return(hwndDde != NULL);
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private VOID APIENTRY DdeSendConnect(ATOM aApp, ATOM aTopic)
{
    fInitiate = fTrue;
    SendMessage(
        (HWND)-1,
        WM_DDE_INITIATE,
        (WPARAM)hwndDde,
        MAKELONG(aApp, aTopic)
        );
    fInitiate = fFalse;
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private BOOL APIENTRY FDdeConnect(SZ szApp, SZ szTopic)
{
    BOOL   fStatus = fTrue;
    MSG    rMsg;
    HANDLE hProcess = NULL;

    //
    // Form the Global Atoms used to indicate the app and topic
    //

	ATOM aApp   = GlobalAddAtom(szApp);
	ATOM aTopic = GlobalAddAtom(szTopic);

    //
    // Connect to the progman dde server
    //

    DdeSendConnect(aApp, aTopic);

    if (hwndProgMan == NULL) {

        //
        // If the connect failed then try to run progman.
        //

        if ((hProcess = ExecuteApplication("PROGMAN /NTSETUP", SW_SHOWNORMAL)) == NULL ) {
            KdPrint(("Failed to execute progman\n"));
            fStatus = fFalse;

        }
        else {
            INT i;
            DWORD dw;
            #define TIMEOUT_INTERVAL  120000

            //
            // Indicate that Progman has been execed
            //

            fProgManExeced = fTrue;

            //
            // exec was successful, first wait for input idle
            //

            if( (dw = WaitForInputIdle( hProcess, TIMEOUT_INTERVAL )) != 0 ) {
                CloseHandle( hProcess );
                fStatus = fFalse;
            }
            else {
                CloseHandle( hProcess );

                //
                // Empty the message queue till no messages
                // are left in the queue or till WM_ACTIVATEAPP is processed. Then
                // try connecting to progman.  I am using PeekMessage followed
                // by GetMessage because PeekMessage doesn't remove some messages
                // ( WM_PAINT for one ).
                //

                while ( PeekMessage( &rMsg, hwndFrame, 0, 0, PM_NOREMOVE ) &&
                        GetMessage(&rMsg, NULL, 0, 0) ) {

                    if (TRUE
                            && (hwndProgressGizmo == NULL
                                || !IsDialogMessage(hwndProgressGizmo, &rMsg))) {
                        TranslateMessage(&rMsg);
                        DispatchMessage(&rMsg);
                    }

                    if ( rMsg.message == WM_ACTIVATEAPP ) {
                        break;
                    }

                }
                DdeSendConnect(aApp, aTopic);
            }
        }
    }

    //
    // Delete the atom resources
    //

	GlobalDeleteAtom(aApp);
    GlobalDeleteAtom(aTopic);

    return ( fStatus );
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private BOOL APIENTRY FDdeWait(VOID)
{
    MSG   rMsg;
    BOOL  fResult   = fTrue;
    DWORD dwTimeOut, dwTickDelta, dwLastTick, dwCurrentTick;

	Assert(hwndProgMan != NULL);
	Assert(hwndDde != NULL);

    //
    // Set timeout for 30 seconds from now.  This assumes that it will
    // take less than 30 seconds for Progman to respond.
    //

    dwTimeOut  = 30000L;
    dwLastTick = GetTickCount();

    while (TRUE) {

        //
        // While there is a connection established to progman and there
        // are DDE messages we can fetch, fetch the messages dispatch them
        // and try to find out if they are terminators (data, ack or terminate)
        //

        while (
            hwndProgMan != NULL &&
            PeekMessage(&rMsg, NULL, WM_DDE_FIRST, WM_DDE_LAST, PM_REMOVE)
            ) {

            TranslateMessage(&rMsg);
            DispatchMessage(&rMsg);

            if (rMsg.wParam == (WPARAM)hwndProgMan) {
                switch (rMsg.message) {

                case WM_DDE_ACK:
                    return ( fAck );

                case WM_DDE_DATA:
                    return (fTrue);

                default:
                    break;
                }
            }
        }


        //
        // If connection to progman has been broken, this may be resulting
        // from a terminate, so return true
        //

        if (hwndProgMan == NULL) {
            return (fTrue);
        }

        //
        // Check to see if timeout hasn't been reached.  If the timeout is
        // reached we will assume that our command succeeded (for want of
        // a better verification scheme
        //
        dwTickDelta = ((dwCurrentTick = GetTickCount()) < dwLastTick) ?
                             dwCurrentTick : (dwCurrentTick - dwLastTick);

        if (dwTimeOut < dwTickDelta) {
            return (fTrue);
        }

        dwTimeOut  = dwTimeOut - dwTickDelta;
        dwLastTick = dwCurrentTick;

        //
        // Lastly, since user doesn't have idle detection, we will be
        // sitting in a tight loop here.  To prevent this just do a
        // sleep for 250 milliseconds.
        //

        Sleep( 250 );

    }

    return(fTrue);
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private BOOL APIENTRY FDdeExec(SZ szCmd)
{
	BOOL   bResult = fFalse;
	HANDLE hCmd;

	Assert(hwndProgMan != NULL);
	Assert(hwndDde != NULL);

    hCmd = GlobalAlloc(GMEM_DDESHARE, (LONG)CchpStrLen(szCmd) + 1);
    if (hCmd != NULL) {

		LPSTR lpCmd = GlobalLock(hCmd);

        if (lpCmd != NULL) {
			lstrcpy(lpCmd, szCmd);
            GlobalUnlock(hCmd);
            MPostWM_DDE_EXECUTE(hwndProgMan, hwndDde, hCmd);
            bResult = FDdeWait();
        }

        else {
            GlobalFree(hCmd);
        }
    }

	return(bResult);
}


/*
**	Purpose:
**	Arguments:
**	Returns:
**
**************************************************************************/
_dt_private BOOL APIENTRY FActivateProgMan(VOID)
{
    //
    // Find out if the dde client window has been started, if not start it
    //

    if (hwndDde == NULL) {
        if (!FDdeInit(NULL)) {
            return(fFalse);
        }
		Assert(hwndDde != NULL);
    }

    //
    // Find out if the connection has been established with the progman
    // server, if not try to connect
    //

    if (hwndProgMan == NULL) {
        //
        // Try to conncect and then see if we were successful
        //
        if ( (!FDdeConnect(szProgMan, szProgMan)) ||
             (hwndProgMan == NULL)
           ) {
            return(fFalse);
        }
    }

    //
    // Bring progman to the foreground
    //

    SetForegroundWindow(hwndProgMan);

    //
    // If progman is iconic restore it
    //

    if (GetWindowLong(hwndProgMan, GWL_STYLE) & WS_ICONIC) {
        ShowWindow(hwndProgMan, SW_RESTORE);
    }

	return(fTrue);
}


/*
**	Purpose:
**		Creates a new Program Manager group.
**	Arguments:
**		Valid command options:
**			cmoVital
**	Notes:
**		Initializes and activates the DDE communication if it is not
**		currently open.
**	Returns:
**		fTrue if group was created, or already existed
**		fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY FCreateProgManGroup(SZ szGroup, SZ szPath, CMO cmo, BOOL CommonGroup)
{
    static CHP szCmdBase[] = "[CreateGroup(%s%s%s,%s)]";
	CCHP cchp;
    char szBuf[BIG_ENUF];
	BOOL fVital = cmo & cmoVital;
	EERC eerc;

    if (szPath == NULL) {
        szPath = "";
    }

    FActivateProgMan();

    wsprintf(szBuf, szCmdBase, szGroup, (*szPath ? "," : szPath), szPath, CommonGroup ? "1" : "0");

    FDdeExec(szBuf);

	return(fTrue);
}


/*
**	Purpose:
**		Removes a Program Manager group.
**	Arguments:
**		Valid command options:
**			cmoVital
**	Notes:
**		Initializes and activates the DDE communication if it is not
**		currently open.
**	Returns:
**		fTrue if successful if removed, or didn't exist
**		fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY FRemoveProgManGroup(SZ szGroup, CMO cmo, BOOL CommonGroup)
{
    static CHP szCmdBase[] = "[DeleteGroup(%s,%s)]";
	CCHP cchp;
    char szBuf[BIG_ENUF];
	BOOL fVital = cmo & cmoVital;
	EERC eerc;

    FActivateProgMan();

    wsprintf(szBuf, szCmdBase, szGroup, CommonGroup ? "1" : "0");

    FDdeExec(szBuf);

	return(fTrue);
}


/*
**	Purpose:
**		Shows a program manager group in one of several different ways
**		based upon the parameter szCommand.
**	Arguments:
**		szGroup:   non-NULL, non-empty group to show.
**		szCommand: non-NULL, non-empty command to exec.
**		cmo:       Valid command options - cmoVital and cmoNone.
**	Notes:
**		Initializes and activates the DDE communication if it is not
**		currently open.
**	Returns:
**		Returns fTrue if successful, fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY FShowProgManGroup(SZ szGroup, SZ szCommand, CMO cmo, BOOL CommonGroup)
{
    static CHP szCmdBase[] = "[ShowGroup(%s, %s,%s)]";
	CCHP cchp;
    SZ   szBuf[BIG_ENUF];
	BOOL fVital = cmo & cmoVital;
	EERC eerc;

	ChkArg(szGroup   != (SZ)NULL && *szGroup   != '\0', 1, fFalse);
	ChkArg(szCommand != (SZ)NULL && *szCommand != '\0', 2, fFalse);

    FActivateProgMan();

    wsprintf(szBuf, szCmdBase, szGroup, szCommand, CommonGroup ? "1" : "0");

    FDdeExec(szBuf);

	return(fTrue);
}


/*
**	Purpose:
**		Creates a new Program Manager item.
**		Always attempts to create the group if it doesn't exist.
**	Arguments:
**		Valid command options:
**			cmoVital
**			cmoOverwrite
**	Notes:
**		Initializes and activates the DDE communication if it is not
**		currently open.
**	Returns:
**		Returns fTrue if successful, fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY
FCreateProgManItem(
    SZ  szGroup,
    SZ  szItem,
    SZ  szCmd,
    SZ  szIconFile,
    INT nIconNum,
    CMO cmo,
    BOOL CommonGroup
    )
{
    static CHP szCmdBase[] = "[AddItem(%s, %s, %s, %d)]";

	CCHP cchp;
    char szBuf[BIG_ENUF];
	BOOL fVital = cmo & cmoVital;
    EERC eerc;
    BOOL bStatus;

    FActivateProgMan();

    wsprintf(szBuf, szCmdBase, szCmd, szItem, szIconFile, nIconNum+666);

    bStatus = FDdeExec(szBuf);

    return(bStatus);
}


/*
**	Purpose:
**		Removes a program manager item.
**	Arguments:
**		Valid command options:
**			cmoVital
**	Returns:
**		Returns fTrue if successful, fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY FRemoveProgManItem(SZ szGroup, SZ szItem, CMO cmo, BOOL CommonGroup)
{
    static CHP szCmdBase[] = "[DeleteItem(%s)]";

	CCHP cchp;
    char szBuf[BIG_ENUF];
	BOOL fVital = cmo & cmoVital;
    EERC eerc;
    BOOL bStatus;

    FActivateProgMan();

    FCreateProgManGroup(szGroup, NULL, cmoVital, CommonGroup);

    wsprintf(szBuf, szCmdBase, szItem);

    bStatus = FDdeExec(szBuf);

    return(bStatus);

}


/*
**	Purpose:
**		Initializes the DDE window for communication with ProgMan
**		Does not actually initiate a conversation with ProgMan
**	Arguments:
**		hInst	instance handle for the setup application
**	Returns:
**		Returns fTrue if successful, fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY FInitProgManDde(HANDLE hInst)
{
    if (hwndDde == NULL) {
        return(FDdeInit(hInst));
    }

	return(fTrue);
}


/*
**	Purpose:
**		Closes conversation with ProgMan (if any) and destroys
**		the DDE communication window (if any)
**	Arguments:
**		(none)
**	Returns:
**		Returns fTrue if successful, fFalse otherwise.
**
**************************************************************************/
_dt_private BOOL APIENTRY FEndProgManDde(VOID)
{

    //
    // if we execed progman then we should try to close it down.  When we
    // send a close message it will post us a WM_DDE_TERMINATE message
    // eventaully.  else we haven't started progman so we just need to
    // terminate the connection.
    //

    if (fProgManExeced) {

        fProgManExeced = fFalse;

        //
        // Clean up connection to progman
        //

        if (hwndProgMan) {
            SetForegroundWindow(hwndFrame);
            UpdateWindow(hwndFrame);
            FDdeExec("[exitprogman(1)]");  // close save state
            hwndProgMan = NULL;
        }

        //
        // Destroy the DDE Window if need be
        //

        if (hwndDde) {
            DestroyWindow(hwndDde);
            hwndDde = NULL;
        }

    }

    else if (hwndProgMan != NULL) {
        EvalAssert( FDdeTerminate() );
    }

    else if (hwndDde != NULL) {
        DestroyWindow (hwndDde);
        hwndDde = NULL;
    }

    return (fTrue);

}


/*
**	Purpose:
**  Arguments:
**  Returns:
**
**************************************************************************/
HANDLE
ExecuteApplication(
    LPSTR lpApp,
    WORD  nCmdShow
    )
{
    BOOL                fStatus;
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;

#if DBG
    DWORD               dwLastError;
#endif

    //
    // Initialise Startup info
    //

    si.cb = sizeof(STARTUPINFO);
    si.lpReserved = NULL;
    si.lpDesktop = NULL;
    si.lpTitle = NULL;
    si.dwX = si.dwY = si.dwXSize = si.dwYSize = 0L;
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = nCmdShow;
    si.lpReserved2 = NULL;
    si.cbReserved2 = 0;

    //
    // Execute using Create Process
    //

    fStatus = CreateProcess(
                  (LPSTR)NULL,                  // lpApplicationName
                  lpApp,                        // lpCommandLine
                  (LPSECURITY_ATTRIBUTES)NULL,  // lpProcessAttributes
                  (LPSECURITY_ATTRIBUTES)NULL,  // lpThreadAttributes
                  DETACHED_PROCESS,             // dwCreationFlags
                  FALSE,                        // bInheritHandles
                  (LPVOID)NULL,                 // lpEnvironment
                  (LPSTR)NULL,                  // lpCurrentDirectory
                  (LPSTARTUPINFO)&si,           // lpStartupInfo
                  (LPPROCESS_INFORMATION)&pi    // lpProcessInformation
                  );

    //
    // Since we are execing a detached process we don't care about when it
    // exits.  To do proper book keeping, we should close the handles to
    // the process handle and thread handle
    //

    if (fStatus) {
        CloseHandle( pi.hThread );
        return( pi.hProcess );
    }
#if DBG
    else {
        dwLastError = GetLastError();
    }
#endif

    //
    // Return the status of this operation

    return ( (HANDLE)NULL );
}