
//  WinExt 1.0	by Fran Finnegan (76244,145)
//  Copyright (c) 1991 Finnegan O'Malley & Company Inc.  All Rights Reserved.
//  First Published in PC Magazine, June 25, 1991

//  developed using Microsoft C 5.10 (Medium memory model only)

#include <direct.h>
#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NOCOMM	//  dumb
#include <windows.h>

#include "winext.h"
#include "dlg.h"

//////////////////////////////////////////////////////////////////////

#define WINEXT_OPTIONS	1
#define WINEXT_DIR	1
#define WINEXT_APP	2

#define SHOW_MINIMIZED	SW_SHOWMINNOACTIVE
#define SHOW_MAXIMIZED	SW_SHOWMAXIMIZED

#define NO		0
#define YES		1

#define argc		__argc
#define argv		__argv

extern	int		argc;
extern	char		**argv;

typedef struct
	{
	int		iCmdShow;
	LPSTR		lpsCmdLine;
	HANDLE		hPrevInst;
	HANDLE		hInst;
	// stack frame prior to WinMain
	}		NEAR *NPWINMAIN;

extern	int	PASCAL	WinMain
	(
	HANDLE		ahInst,
	HANDLE		ahPrevInst,
	LPSTR		alpsCmdLine,
	int		aiCmdShow
	);

extern	int FAR PASCAL	DlgProc
	(
	HWND		ahDlg,
	WORD		awMsg,
	WORD		awParam,
	DWORD		alParam
	);

#pragma alloc_text(	   _TEXT, WinMain			)
#pragma alloc_text( DLGPROC_TEXT, DlgProc			)

#define Local(w)	LocalLock(LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, w))

#define sizeof_mpsBuffer	(512		)
#define sizeof_mpsCmdLine	(_MAX_PATH * 2	)
#define sizeof_mpsWinIniExt	(256		)
#define sizeof_mpsFileName	(_MAX_PATH	)
#define sizeof_mpOf		sizeof(OFSTRUCT)

static	NPWINMAIN	mnpWinMain;
static	HWND		mhDlg;	//  required for Usage recursion
static	POFSTRUCT	mpOf;	//  contains the d:\path\FILENAME.EXT
static	BOOL		sbUsage;
static	char		*mpsBuffer	,
			*mpsCmdLine	,	//  the New Command Line
			*mpsWinIniExt	,	//  ext=string
			*mpsFileName	;	//  d:\path\FILENAME

/******************************************************************************
Usage:	WIN.INI [Extensions]:	ext=winext [?-+] dir app [args] ^.ext [args]
	Program Manager item:	winext.exe [?-+] dir app [args] file

where:	ext	Extension of associated data file (EXT part of FILENAME.EXT)
	winext	WinExt (WINEXT.EXE should be somewhere on the environment PATH)
	?-+	Optional WinExt arguments:  ?=dialog; -=minimize; +=maximize
	dir	Current Working Directory:  start-up directory (path, . or ..)
	app	Program to run:  .EXE, .COM, .BAT or .PIF file specification
	args	Optional app arguments (for embedded blanks, use double-quotes)
	^.ext	File placeholder:  may be ^, ^. or * (ignore .ext, ext or file)
	args	More optional app arguments (WIN.INI [Extensions] entry only)
	file	Qualified file specification:  may be .\file or * (ignore file)

winext, dir, app, ^.ext and file are required; ?-+ and args are optional ([]s).
dir may be subsequently overridden by a Start-up Directory in an app .PIF file.
path\FILENAME substitutions are made for multiple occurrences of the caret (^).
Environment-variable substitutions are made just as in .BAT files:  %variable%.
******************************************************************************/

extern	int	PASCAL	WinMain
	(
	HANDLE		ahInst,
	HANDLE		ahPrevInst,
	LPSTR		alpsCmdLine,
	int		aiCmdShow
	)
{
auto	char		*apsFirst,
			*apsLast,
			*apsEnv;
auto	FARPROC 	aFpDlgProc;
auto	WORD		awCode;
auto	int		aiDialogBox	= NO,
			aiFileAsterisk	= NO;
auto	MSG		aMsg;

static	char		sszError[] = "Error.",
			sszBlank[] = " ",
			sszQuote[] = "\"";

if (NULL == mnpWinMain) 	//  ignore recursion from dialog proc
    {
    //	possibly become a "TSR" on a Load with no arguments
    if (0 == ahPrevInst 	//  look for a previous "TSR" instance
    &&	1 == argc)		//  only do this on the first instance
	switch (aiCmdShow)
	    {
	    case SW_HIDE	   :
	    case SW_SHOWMINIMIZED  :
	    case SW_MINIMIZE	   :
	    case SW_SHOWMINNOACTIVE:
		while (YES)	//  go into an infinite loop
		    PeekMessage(&aMsg, 0, WM_NULL, WM_NULL, PM_REMOVE);
		//  nothing below this line will be executed
	    }

    //	remember the first set of WinMain arguments for dialog proc
    mnpWinMain = (NPWINMAIN)&aiCmdShow;

    //	allocate all local storage from largest to smallest
    mpsBuffer		= Local(sizeof_mpsBuffer   );
    mpsCmdLine		= Local(sizeof_mpsCmdLine  );
    mpsWinIniExt	= Local(sizeof_mpsWinIniExt);
    mpsFileName 	= Local(sizeof_mpsFileName );
    mpOf     = (POFSTRUCT)Local(sizeof_mpOf	   );

    //	verify that allocations succeeded
    if (mpsBuffer
    &&	mpsCmdLine
    &&	mpsWinIniExt
    &&	mpsFileName
    &&	mpOf		)
	lstrcpy(mpsCmdLine, alpsCmdLine);	//  to check for '^'
    else	//  a LocalAlloc failed
	{
	MessageBox(0, sszError, "WinExt", MB_ICONSTOP | MB_OK);
	return 0;
	}
    }

//  look for the WinExt options:  ?=dialog; -=minimize; +=maximize
if (2 <= argc)
    switch (*argv[WINEXT_OPTIONS])
	{
	case '?':
	case '-':
	case '+':
	    while (*argv[WINEXT_OPTIONS])
		switch (*(argv[WINEXT_OPTIONS]++))
		    {
		    default :	//  bad option, so show dialog box
		    case '?':   aiDialogBox = YES;              break;
		    case '-':   aiCmdShow = SHOW_MINIMIZED;     break;
		    case '+':   //  if minimize, don't maximize
			switch (aiCmdShow)
			    {
			    default:
				aiCmdShow = SHOW_MAXIMIZED;	break;
			    case SW_HIDE	   :
			    case SW_SHOWMINIMIZED  :
			    case SW_MINIMIZE	   :
			    case SW_SHOWMINNOACTIVE:		break;
			    }
			break;
		    }
	    argc--;  argv++;	//  hide WinExt options
	    break;
	}

//  there must be at least three arguments:  winext dir app ^.ext
if (4 <= argc--
&&  NULL == strchr(mpsCmdLine, '^'))    //  '^' not allowed on Command Line
    {
    //	argv[argc] is probably the ^.ext (FILENAME.EXT), because
    //	    everything after the '^' is ignored by most shells

    //	check for '*', which means no FILENAME.EXT is to be used
    if ('*' == argv[argc][0])
	aiFileAsterisk = YES;	//  turn file-asterisk flag on
    else
	{
	//  use the partially- or fully-qualified d:\path\FILENAME.EXT
	lstrcpy(mpOf->szPathName, argv[argc]);
	if (mpOf->szPathName[0] != '\\'  //   \filespec
	&&  mpOf->szPathName[0] != '.'   //  .\filespec or ..\filespec
	&&  mpOf->szPathName[1] != ':')  //  d:filespec or d:\filespec
	    OpenFile(argv[argc], mpOf, OF_PARSE);  //  fully-qualified

	//  get the d:\path\FILENAME (no .EXT) for '^' substitution
	lstrcpy(mpsFileName, mpOf->szPathName);
	if (apsLast = strrchr(mpsFileName, '.'))  //  if there's a '.'
	    {
	    *apsLast = '\0';                    //  remove the .EXT
	    lstrcpy(mpsWinIniExt, ++apsLast);	//  get the EXT
	    lstrcat(mpsWinIniExt, "=");         //  add the '='

	    //	use EXT to get the WIN.INI [Extensions] ext= string
	    GetProfileString("Extensions", apsLast, "",
			   mpsWinIniExt + lstrlen(mpsWinIniExt),
		    sizeof_mpsWinIniExt - (3 + 1));
	    }
	}

    //	change the Current Working Directory to dir
    if (('.' == argv[WINEXT_DIR][0]     //  check for '.' only
    &&	'\0' == argv[WINEXT_DIR][1])
    ||	(':' == argv[WINEXT_DIR][1]     //  check for "d:" only
    &&	'\0' == argv[WINEXT_DIR][2])
    ||	0 == chdir(argv[WINEXT_DIR]))	//  change directory
	{
	//  if dir is valid, change drive to any d: in dir
	if (':' == argv[WINEXT_DIR][1])         //  look for "d:"
	    _dos_setdrive(argv[WINEXT_DIR][0] & 0x1F, &awCode);

	//  create the New Command Line, beginning with app
	lstrcpy(mpsCmdLine, argv[WINEXT_APP]);

	//  append any/all args on the left of the FILENAME.EXT
	for (argc -= 3, argv += 3;  argc;  argc--, argv++)
	    {
	    lstrcat(mpsCmdLine, sszBlank);	//  append ' '
	    if (apsFirst = strchr(*argv, ' '))  //  embedded ' '?
		lstrcat(mpsCmdLine, sszQuote);	//    prepend '"'
	    lstrcat(mpsCmdLine, *argv); 	//  then argument
	    if (apsFirst)			//  embedded ' '?
		lstrcat(mpsCmdLine, sszQuote);	//    append '"'
	    }

	//  add the d:\path\FILENAME.EXT
	if (NO == aiFileAsterisk)		//  if no '*'
	    {
	    lstrcat(mpsCmdLine, sszBlank);	//  append ' ' and
	    lstrcat(mpsCmdLine, mpOf->szPathName);  //	the filespec
	    }

	//  look in ext= string for '^' and args on right
	if (apsFirst = strchr(mpsWinIniExt, '^'))
	    {
	    //	assuming the first '^' in ext=, remove the old .EXT
	    *strrchr(mpsCmdLine, '.') = '\0';

	    //	append ext= string .ext and any args on the right
	    lstrcat(mpsCmdLine, apsFirst + 1);
	    }

	//  do any other substitutions for '^' of d:\path\FILENAME
	while (apsFirst = strchr(mpsCmdLine, '^'))  //  if there's '^'
	    {
	    lstrcpy(mpsBuffer, mpsFileName);	// d:\path\FILENAME
	    lstrcat(mpsBuffer, apsFirst + 1);	// post-'^' stuff
	    lstrcpy(apsFirst, mpsBuffer);	// New Command Line
	    }

	//  do any environment %variable% string substitutions
	while ((apsFirst = strchr(mpsCmdLine  , '%'))
	&&     (apsLast  = strchr(apsFirst + 1, '%')))
	    {
	    *apsLast = mpsBuffer[0] = '\0';     // remove last '%'
	    if ((apsEnv = getenv(	apsFirst + 1 ))   // UC/lc
	    ||	(apsEnv = getenv(strupr(apsFirst + 1))))  // UC
		lstrcpy(mpsBuffer, apsEnv);	// env variable
	    lstrcat(mpsBuffer, apsLast + 1);	// post-'%' stuff
	    lstrcpy(apsFirst, mpsBuffer);	// New Command Line
	    }

	//  show the dialog box if it was requested or
	//			if the New Command Line is too long
	apsFirst = strchr(mpsCmdLine, ' ');     //  first blank after app
	if (NULL == apsFirst)			//  no blank after app
	    apsFirst = mpsCmdLine + lstrlen(mpsCmdLine);
	if (aiDialogBox
	|| (apsFirst - mpsCmdLine + 128) < lstrlen(mpsCmdLine))   //  too long?
	    {
	    aiDialogBox = DialogBox(ahInst, MAKEINTRESOURCE(DLG), 0,
			    aFpDlgProc = MakeProcInstance(DlgProc, ahInst));
	    FreeProcInstance(aFpDlgProc);
	    if (ID_PB_CANCEL == aiDialogBox)
		return 0;   //	premature termination on Cancel
	    }

	//  execute the New Command Line
	if (32 < (awCode = WinExec(mpsCmdLine, aiCmdShow)))
	    return 0;	//  normal termination

	//  if above Exec fails, fall through and report error code
	}
    else    //	requested dir is invalid
	{
	awCode = 3;	//  error code for "path not valid" string
	lstrcpy(mpsCmdLine, argv[WINEXT_DIR]);	//  caption is dir
	}

    //	load error text from .RC STRINGTABLE and show message box
    if (0 == LoadString(ahInst, awCode, mpsBuffer, sizeof_mpsBuffer))
	lstrcpy(mpsBuffer, sszError);	//  unknown-code string
    MessageBox(0, mpsBuffer, mpsCmdLine, MB_ICONSTOP | MB_OK);
    }

//  put up a "usage" dialog box if requested or if something's wrong
sbUsage = YES;
DialogBox(ahInst, MAKEINTRESOURCE(USAGE), mhDlg,
	aFpDlgProc = MakeProcInstance(DlgProc, ahInst));
FreeProcInstance(aFpDlgProc);
sbUsage = NO;
return 0;   //	error termination
}

//////////////////////////////////////////////////////////////////////

extern	int FAR PASCAL	DlgProc
	(
	HWND		ahDlg,
	WORD		awMsg,
	WORD		awParam,
	DWORD		alParam
	)
{
auto	WORD		awCode,
			awCmdLen;
auto	RECT		aRt;
auto	char		*apsCmdLine;

static	HWND		shCtlExecute,
			shCtlCancel ,
			shCtlReset  ,
			shCtlUsage  ,
			shCtlExt    ,
			shCtlCWD    ,
			shCtlPIF    ,
			shCtlCmd    ,
			shCtlDefID  ;
static	WORD		swCmdLen;
static	int		siID_RB_SW;
static	char		sszPIF[] = ".PIF";

switch (awMsg)
    {
    case WM_INITDIALOG:
	//  center dialog box
	GetWindowRect(ahDlg, &aRt);
	OffsetRect(&aRt, -aRt.left  , -aRt.top	 );
	MoveWindow(ahDlg,   //	this is the correct way to center a dialog:
		((GetSystemMetrics(SM_CXSCREEN) - aRt.right ) / 2 + 4) & ~7,
		 (GetSystemMetrics(SM_CYSCREEN) - aRt.bottom) / 2,
		aRt.right , aRt.bottom, NO);

	//  set dialog caption to module name
	GetModuleFileName(mnpWinMain->hInst, mpsBuffer, sizeof_mpsBuffer);
	SetWindowText(ahDlg, mpsBuffer);

	//  if "usage" dialog box, done
	if (sbUsage)
	    {
	    SetDlgItemText(ahDlg, ID_E_CMD, mnpWinMain->lpsCmdLine);
	    return YES;     //	did process the message
	    }

	//  save all relevant handles
	shCtlDefID   =
	shCtlExecute = GetDlgItem(ahDlg, ID_PB_EXECUTE);
	shCtlCancel  = GetDlgItem(ahDlg, ID_PB_CANCEL );
	shCtlReset   = GetDlgItem(ahDlg, ID_PB_RESET  );
	shCtlUsage   = GetDlgItem(ahDlg, ID_PB_USAGE  );
	shCtlExt     = GetDlgItem(ahDlg, ID_T_EXT     );
	shCtlCWD     = GetDlgItem(ahDlg, ID_T_CWD     );
	shCtlPIF     = GetDlgItem(ahDlg, ID_T_PIF     );
	shCtlCmd     = GetDlgItem(ahDlg, ID_E_CMD     );

	//  set ext= text controls
	if (mpsWinIniExt[0])	//  WIN.INI [Extensions]
	    SetWindowText(shCtlExt, mpsWinIniExt);
	else			//  WinExt Command Line
	    {
	    GetWindowText(shCtlExt, mpsBuffer, sizeof_mpsBuffer);
	    SetWindowText(GetDlgItem(ahDlg, ID_GB_EXTBOX), mpsBuffer);
	    SetWindowText(shCtlExt, mnpWinMain->lpsCmdLine);
	    }

	//  set New Command Line edit control
	apsCmdLine = strchr(mpsCmdLine, ' ');   //  first blank after app
	if (NULL == apsCmdLine) 		//  no blank after app
	    apsCmdLine = mpsCmdLine + lstrlen(mpsCmdLine);
	swCmdLen = apsCmdLine - mpsCmdLine + 128;   //	maximum command length
	if (swCmdLen < lstrlen(mpsCmdLine))	//  is New Command Line longer?
	    {
	    GetWindowText(shCtlCWD, mpsBuffer, sizeof_mpsBuffer);  //  caption
	    MessageBox(0, mpsCmdLine, mpsBuffer, MB_ICONEXCLAMATION | MB_OK);
	    mpsCmdLine[swCmdLen] = '\0';        //  arguments:  ' ' + 127 bytes
	    }
	SendMessage(shCtlCmd, EM_LIMITTEXT, swCmdLen, 0L);
	SetWindowText(shCtlCmd, mpsCmdLine);	//  New Command Line

	//  set cwd text control		//  caption of above MessageBox
	getcwd(mpsBuffer, sizeof_mpsBuffer);	//  no trailing '\'
	SetWindowText(shCtlCWD, mpsBuffer);	//  Current Working Directory

	//  search for a .PIF file
	lstrcpy(mpsBuffer , mpsCmdLine);
	if ( apsCmdLine = strchr(mpsBuffer, ' '))       //  use only app
	    *apsCmdLine = '\0';
	if (':' == mpsBuffer[1])                        //  remove any d:
	    lstrcpy(mpsBuffer , mpsBuffer + 2);
	if ( apsCmdLine = strrchr(mpsBuffer, '\\'))     //  remove any path
	    lstrcpy(mpsBuffer , apsCmdLine + 1);
	if ( apsCmdLine = strchr(mpsBuffer, '.'))       //  rename to .PIF
	    lstrcpy(apsCmdLine, sszPIF);
	else
	    lstrcat(mpsBuffer , sszPIF);
	if (-1 < OpenFile(mpsBuffer , mpOf, OF_EXIST))	//  see if .PIF exists
	    SetWindowText(shCtlPIF, mpOf->szPathName);
	else						//  hide .PIF control
	    MoveWindow(shCtlPIF, 0, 0, 0, 0, NO);

	//  set the radio button based on iCmdShow
	switch (mnpWinMain->iCmdShow)
	    {
	//  case SW_SHOWNORMAL	   :
	//  case SW_SHOWNOACTIVATE :
	//  case SW_SHOW	   :
	//  case SW_SHOWNA	   :
	//  case SW_RESTORE	   :
	    default		   :	siID_RB_SW = ID_RB_DEF;     break;

	    case SW_HIDE	   :
	    case SW_SHOWMINIMIZED  :
	    case SW_MINIMIZE	   :
	    case SW_SHOWMINNOACTIVE:	siID_RB_SW = ID_RB_MIN;     break;

	    case SW_SHOWMAXIMIZED  :	siID_RB_SW = ID_RB_MAX;     break;
	    }
	CheckRadioButton(ahDlg, ID_RB_DEF, ID_RB_MAX, siID_RB_SW);

	return YES;	//  did process the message

    case WM_COMMAND:
	awCode = HIWORD(alParam);
	switch (awParam)
	    {
	    case ID_E_CMD:
		//  enable/disable Execute based on the New Command Line
		if (GetWindowTextLength(shCtlCmd))
		    {
		    shCtlDefID = shCtlExecute;
		    SendMessage(ahDlg, DM_SETDEFID, ID_PB_EXECUTE, 0L);
		    EnableWindow(shCtlExecute, YES);
		    }
		else
		    {
		    shCtlDefID = shCtlCancel ;
		    SendMessage(ahDlg, DM_SETDEFID, ID_PB_CANCEL , 0L);
		    EnableWindow(shCtlExecute, NO );
		    }

		if (awCode == EN_CHANGE)
		    {
		    //	enable Reset based on the changed New Command Line
		    EnableWindow(shCtlReset, YES);

		    //	adjust the maximum size of the New Command Line
		    GetWindowText(shCtlCmd, mpsBuffer, sizeof_mpsBuffer);
		    apsCmdLine = strchr(mpsBuffer, ' ');    //  first blank
		    if (NULL == apsCmdLine)		//  no blank after app
			apsCmdLine = mpsBuffer + lstrlen(mpsBuffer);
		    awCmdLen = apsCmdLine - mpsBuffer + 128;
		    if (awCmdLen < lstrlen(mpsBuffer))
			MessageBeep(0);
		    SendMessage(shCtlCmd, EM_LIMITTEXT, awCmdLen, 0L);
		    }
		break;

	    case ID_RB_DEF:
	    case ID_RB_MIN:
	    case ID_RB_MAX:
		//  enable Reset based on clicked radio buttons
		if (awCode == BN_CLICKED
		||  awCode == BN_DOUBLECLICKED)
		    if (awParam != siID_RB_SW)
			EnableWindow(shCtlReset, YES);
		break;
	    }
	switch (awParam)
	    {
	    case ID_PB_RESET:
		//  move the focus to Reset, if it's not there
		SetFocus(shCtlReset);

		//  reset the New Command Line edit control
		SendMessage(shCtlCmd, EM_LIMITTEXT, swCmdLen, 0L);
		SetWindowText(shCtlCmd, mpsCmdLine);
		SendMessage(shCtlCmd, EM_SETSEL, 0, MAKELONG(0, 0xFFFF));
		SetFocus(shCtlCmd);

		//  reset the radio button controls
		CheckRadioButton(ahDlg, ID_RB_DEF, ID_RB_MAX, siID_RB_SW);

		//  reset the button styles
		SendMessage(shCtlDefID, BM_SETSTYLE, BS_DEFPUSHBUTTON, 1L);
		SendMessage(shCtlReset, BM_SETSTYLE, BS_PUSHBUTTON   , 1L);
		//  fall through
	    case ID_E_CMD:
	    case ID_RB_DEF:
	    case ID_RB_MIN:
	    case ID_RB_MAX:
		//  disable Reset based on New Command Line and radio buttons
		if (IsDlgButtonChecked(ahDlg, siID_RB_SW))
		    {
		    GetWindowText(shCtlCmd, mpsBuffer, sizeof_mpsBuffer);
		    if (0 == strcmp(mpsBuffer, mpsCmdLine))
			EnableWindow(shCtlReset, NO);
		    }
		break;

	    case ID_PB_EXECUTE:
		//  if "usage" dialog box, done
		if (sbUsage)
		    {
		    EndDialog(ahDlg, 0);	//  OK
		    break;
		    }

		//  get the changed New Command Line
		GetWindowText(shCtlCmd, mpsCmdLine, sizeof_mpsCmdLine);

		//  remove any CR/LFs that were inserted in the edit control
		while ((apsCmdLine = strchr(mpsCmdLine, '\r'))
		||     (apsCmdLine = strchr(mpsCmdLine, '\n')))
		       *apsCmdLine = ' ';

		//  possibly change iCmdShow
		if (NO == IsDlgButtonChecked(ahDlg, siID_RB_SW))
		    {
			mnpWinMain->iCmdShow = SW_SHOW;
		    if (IsDlgButtonChecked(ahDlg, ID_RB_MIN))
			mnpWinMain->iCmdShow = SHOW_MINIMIZED;
		    if (IsDlgButtonChecked(ahDlg, ID_RB_MAX))
			mnpWinMain->iCmdShow = SHOW_MAXIMIZED;
		    }
		//  fall through
	    case ID_PB_CANCEL:
		EndDialog(ahDlg, awParam);	//  Execute, Cancel or OK
		break;

	    case ID_PB_USAGE:
		//  move the focus to Usage, if it's not there
		SetFocus(shCtlUsage);
		UpdateWindow(ahDlg);

		//  put up a "usage" dialog box
		mhDlg = ahDlg;	//  required for Usage recursion
		argc = 1;	//  for recursion
		WinMain(mnpWinMain->hInst,
			mnpWinMain->hPrevInst,
			mnpWinMain->lpsCmdLine,
			mnpWinMain->iCmdShow);
		mhDlg = 0;	//  required for Usage recursion

		//  move the focus to the New Command Line edit control
		SetFocus(shCtlCmd);

		//  reset the button styles
		SendMessage(shCtlDefID, BM_SETSTYLE, BS_DEFPUSHBUTTON, 1L);
		SendMessage(shCtlUsage, BM_SETSTYLE, BS_PUSHBUTTON   , 1L);
		break;
	    }
	return YES;	//  did process the message
    }
return NO;	//  didn't process the message
}

