/* $Revision Header * Header built automatically - do not edit! *************
 *
 *	(C) Copyright 1990 by Olaf 'Olsen' Barthel & MXM
 *
 *	Name .....: TermAux.c
 *	Created ..: Monday 21-Jan-91 20:12
 *	Revision .: 0
 *
 *	Date            Author          Comment
 *	=========       ========        ====================
 *	21-Jan-91       Olsen           Created this file!
 *
 * $Revision Header ********************************************************/

#include "TermGlobal.h"

	/* The following static strings are displayed in the status
	 * window.
	 */

STATIC UBYTE *ConfigFont[2] =
{
	"Topaz",
	"IBM  "
};

STATIC UBYTE *ConfigEmulation[3] =
{
	"ANSI/VT",
	"Atomic ",
	"TTY    "
};

STATIC UBYTE *ConfigParity[5] =
{
	"None",
	"Even",
	"Odd ",
	"Mark",
	"Spac"
};

STATIC UBYTE *ConfigStatus[7] =
{
	"Ready   ",
	"Holding ",
	"Dialing ",
	"Upload  ",
	"Download",
	"Breaking",
	"Hang Up "
};

	/* Block window nest count. */

STATIC SHORT BlockNestCount;

	/* GetConUnit():
	 *
	 *	Extract the ConUnit pointer from the current output stream
	 *	console.
	 */

struct ConUnit *
GetConUnit(struct MsgPort *ConsoleTask)
{
	struct InfoData	*InfoData;
	struct ConUnit	*ConUnit = NULL;

	if(!ConsoleTask)
		ConsoleTask = (struct MsgPort *)((struct Process *)SysBase -> ThisTask) -> pr_ConsoleTask;

	if(InfoData = (struct InfoData *)AllocVec(sizeof(struct InfoData),MEMF_PUBLIC|MEMF_CLEAR))
	{
			/* Send the inquiry packet to the console task. */

		if(DoPkt1(ConsoleTask,ACTION_DISK_INFO,MKBADDR(InfoData)))
			ConUnit = (struct ConUnit *)((struct IOStdReq *)InfoData -> id_InUse) -> io_Unit;

		FreeVec(InfoData);
	}

	return(ConUnit);
}

	/* AddDownloadObject(UBYTE *Line):
	 *
	 *	Add another downloaded object to the list.
	 */

VOID
AddDownloadObject(UBYTE *Line)
{
	struct Node *SomeNode;

		/* Allocate space for the node itself and the
		 * string to be put into the node name.
		 */

	if(SomeNode = (struct Node *)AllocVec(sizeof(struct Node) + strlen(Line) + 1,MEMF_PUBLIC|MEMF_CLEAR))
	{
			/* Block list modification. */

		ObtainSemaphore(DownloadSemaphore);

			/* Enter the name. */

		SomeNode -> ln_Name = (UBYTE *)(SomeNode + 1);

			/* Copy the line over. */

		strcpy(SomeNode -> ln_Name,Line);

			/* Add it to the list. */

		AddTail(&DownloadList,SomeNode);

			/* Increment number of downloads. */

		DownloadLineCount++;

		ReleaseSemaphore(DownloadSemaphore);
	}
}

	/* ClearDownloadObjects():
	 *
	 *	Clear the list of downloaded objects.
	 */

VOID
ClearDownloadObjects()
{
	struct Node *SomeNode,*NextNode;

	ObtainSemaphore(DownloadSemaphore);

	SomeNode = DownloadList . lh_Head;

	while(SomeNode -> ln_Succ)
	{
		NextNode = SomeNode -> ln_Succ;

		Remove(SomeNode);

		FreeVec(SomeNode);

		SomeNode = NextNode;
	}

	DownloadNode = NULL;

	DownloadLineCount = 0;

	ReleaseSemaphore(DownloadSemaphore);
}

	/* SequenceFilter(UBYTE Char):
	 *
	 *	Yet another byte sequence filter, this time it's the
	 *	ARexx interface to make the call.
	 */

struct ScanNode *
SequenceFilter(UBYTE Char)
{
	struct ScanNode	*SomeNode,*Matching = NULL;
	BYTE		 Matches = 0;

		/* Convert input character to upper case. */

	Char = ToUpper(Char);

		/* Get the first node in the list. */

	SomeNode = (struct ScanNode *)SequenceList . lh_Head;

		/* Scan until the last node is found. */

	while(SomeNode -> Node . mln_Succ)
	{
			/* This sequence had a couple of matches. */

		if(SomeNode -> Count == SequenceCount)
		{
				/* The next character in the sequence
				 * still matches.
				 */

			if(Char == SomeNode -> Sequence[SequenceCount])
			{
					/* Increase number of matching
					 * characters.
					 */

				SomeNode -> Count++;

					/* If there's another character
					 * in the sequence, increase
					 * the match counter.
					 */

				if(SomeNode -> Sequence[SequenceCount + 1])
					Matches++;
				else
				{
						/* We were able to make
						 * a perfect match.
						 */

					Matches = 0;

					Matching = SomeNode;
				}
			}
		}

			/* Skip to the next node. */

		SomeNode = (struct ScanNode *)SomeNode -> Node . mln_Succ;
	}

	if(!Matches)
	{
			/* Clear the list entry counters. */

		if(SequenceCount)
		{
			SomeNode = (struct ScanNode *)SequenceList . lh_Head;

			while(SomeNode -> Node . mln_Succ)
			{
				SomeNode -> Count = 0;

				SomeNode = (struct ScanNode *)SomeNode -> Node . mln_Succ;
			}

			SequenceCount = 0;
		}
	}
	else
		SequenceCount++;

	return(Matching);
}

	/* AddSequenceObject(UBYTE *Sequence):
	 *
	 *	Add another sequence to the list of byte sequences
	 *	the routine above will look at whilst parsing.
	 */

VOID
AddSequenceObject(UBYTE *Sequence)
{
	struct ScanNode *SomeNode;

	if(SomeNode = AllocVec(sizeof(struct ScanNode) + strlen(Sequence) + 1,MEMF_PUBLIC|MEMF_CLEAR))
	{
		SHORT i;

		SomeNode -> Sequence = (UBYTE *)(SomeNode + 1);

		for(i = 0 ; i <= strlen(Sequence) ; i++)
			SomeNode -> Sequence[i] = ToUpper(Sequence[i]);

		AddTail(&SequenceList,(struct Node *)SomeNode);
	}
}

	/* ClearSequenceObjects():
	 *
	 *	Clear the list of scan sequences.
	 */

VOID
ClearSequenceObjects()
{
	struct Node *SomeNode,*NextNode;

	SomeNode = SequenceList . lh_Head;

	while(SomeNode -> ln_Succ)
	{
		NextNode = SomeNode -> ln_Succ;

		Remove(SomeNode);

		FreeVec(SomeNode);

		SomeNode = NextNode;
	}
}

	/* LogAction(UBYTE *String,...):
	 *
	 *	Write an action to the default log file.
	 */

VOID __stdargs
LogAction(UBYTE *String,...)
{
	if(Config . LogActions && Config . LogFile[0])
	{
		UBYTE	DummyBuffer[256];
		BPTR	File;

		va_list	VarArgs;

			/* Build a valid string. */

		va_start(VarArgs,String);
		VSPrintf(DummyBuffer,String,VarArgs);
		va_end(VarArgs);

			/* Does the log file already exist? */

		if(GetFileSize(Config . LogFile))
		{
				/* It does, let's append the data. */

			if(File = Open(Config . LogFile,MODE_READWRITE))
			{
				if(Seek(File,0,OFFSET_END) == -1)
				{
					Close(File);

					File = NULL;
				}
			}
		}
		else
		{
				/* Create a new file. */

			if(File = Open(Config . LogFile,MODE_NEWFILE))
				FPrintf(File,"Date      Time     Action\n--------- -------- ----------------------------------------\n");
		}

			/* The file is open, build the date/time string and
			 * write the log action.
			 */

		if(File)
		{
			UBYTE		TimeBuffer[20],DateBuffer[20];
			struct DateTime	DateTime;

				/* Obtain current time. */

			DateStamp(&DateTime . dat_Stamp);

				/* Convert it to human readable form. */

			DateTime . dat_Format	= FORMAT_DOS;
			DateTime . dat_Flags	= 0;
			DateTime . dat_StrDay	= NULL;
			DateTime . dat_StrDate	= DateBuffer;
			DateTime . dat_StrTime	= TimeBuffer;

				/* Conversion succeeded? */

			if(DateToStr(&DateTime))
				FPrintf(File,"%s %s %s\n",DateBuffer,TimeBuffer,DummyBuffer);

				/* Done! */

			Close(File);
		}
	}
}

	/* FlushMsg(struct Window *Window):
	 *
	 *	Cancel all pending messages of a window.
	 */

VOID
FlushMsg(struct Window *Window)
{
	struct IntuiMessage *Massage;

	while(Massage = (struct IntuiMessage *)GetMsg(Window -> UserPort))
		ReplyMsg(&Massage -> ExecMessage);
}

	/* Local2Upper(UBYTE c):
	 *
	 *	Very much the same as the macro expression ToUpper, but
	 *	more suitable for expressions such as `ToUpper(*a++)'.
	 */

UBYTE
Local2Upper(UBYTE c)
{
	return((UBYTE)((((c) >= 224 && (c) <= 254) || ((c) >= 'a' && (c) <= 'z')) ? (c) - 32 : (c)));
}

	/* GetString(UBYTE *Prompt,UBYTE *Buffer):
	 *
	 *	Get a string from the user, very much the same as xpr_gets,
	 *	but also including the `Load File' gadget.
	 */

BYTE
GetString(UBYTE *Prompt,UBYTE *Buffer)
{
	struct Gadget		*GadgetList;
	struct Gadget		*GadgetArray[4];
	struct Window		*PanelWindow;
	struct Menu		*PanelMenu;
	UBYTE			 DummyBuffer[256];
	struct FileRequester	*FileRequest;
	LONG			 Width;
	BYTE			 Success = FALSE;

	STATIC struct NewMenu GetStringMenu[] =
	{
		{ NM_TITLE, "Project",		 0 , 0, 0, (APTR)0},
		{  NM_ITEM, "Okay",		"O", 0, 0, (APTR)1},
		{  NM_ITEM, "Cancel",		"C", 0, 0, (APTR)3},
		{  NM_ITEM, NM_BARLABEL,	 0 , 0, 0, (APTR)0},
		{  NM_ITEM, "Load File...",	"L", 0, 0, (APTR)2},
		{  NM_ITEM, NM_BARLABEL,	 0 , 0, 0, (APTR)0},
		{  NM_ITEM, "Quit",		"Q", 0, 0, (APTR)3},
		{ NM_END, 0,			 0 , 0, 0, (APTR)0}
	};

	if(CreateAllGetsGadgets(TRUE,Buffer,Prompt,&Width,&GadgetArray[0],&GadgetList,VisualInfo,Screen -> WBorTop + Screen -> Font -> ta_YSize + 1))
	{
		if(PanelMenu = CreateMenus(GetStringMenu,
			GTMN_FrontPen, 0,
		TAG_DONE))
		{
			if(LayoutMenus(PanelMenu,VisualInfo,
				GTMN_TextAttr,&DefaultFont,
			TAG_DONE))
			{
				if(PanelWindow = OpenWindowTags(NULL,
					WA_Width,	Width,
					WA_Height,	56,
					WA_Left,	(Screen -> Width - Width) >> 1,
					WA_Top,		(Screen -> Height - 56) >> 1,
					WA_Activate,	TRUE,
					WA_DragBar,	TRUE,
					WA_DepthGadget,	TRUE,
					WA_CloseGadget,	TRUE,
					WA_RMBTrap,	TRUE,
					WA_CustomScreen,Screen,
					WA_IDCMP,	IDCMP_GADGETDOWN | IDCMP_ACTIVEWINDOW | IDCMP_CLOSEWINDOW | IDCMP_GADGETUP | IDCMP_MENUPICK | IDCMP_RAWKEY,
					WA_Title,	"Enter A String",
				TAG_DONE))
				{
					struct IntuiMessage	*Massage;
					ULONG			 Class,Code;
					struct Gadget		*Gadget;
					BYTE			 Terminated = FALSE;

					PushWindow(PanelWindow);

					SetMenuStrip(PanelWindow,PanelMenu);

					PanelWindow -> Flags &= ~WFLG_RMBTRAP;

					AddGList(PanelWindow,GadgetList,(UWORD)-1,(UWORD)-1,NULL);
					RefreshGList(GadgetList,PanelWindow,NULL,(UWORD)-1);
					GT_RefreshWindow(PanelWindow,NULL);

					ActiveGadget = GadgetArray[0];

					ActivateGadget(GadgetArray[0],PanelWindow,NULL);

					while(!Terminated)
					{
						WaitPort(PanelWindow -> UserPort);

						while(!Terminated && (Massage = (struct IntuiMessage *)GT_GetIMsg(PanelWindow -> UserPort)))
						{
							Class	= Massage -> Class;
							Code	= Massage -> Code;
							Gadget	= (struct Gadget *)Massage -> IAddress;

							GT_ReplyIMsg(Massage);

							if(Class == IDCMP_GADGETDOWN)
							{
								if((Gadget -> GadgetType & GTYP_GTYPEMASK) == STRGADGET)
									ActiveGadget = Gadget;
							}

							if(Class == IDCMP_RAWKEY)
							{
								if(Code == IECODE_UP_PREFIX|103 && CommandWindow == PanelWindow)
									ActivateGadget(CommandGadget,PanelWindow,NULL);
							}

							if(Class == IDCMP_ACTIVEWINDOW)
								ActivateGadget(GadgetArray[0],PanelWindow,NULL);

							if(Class == IDCMP_MENUPICK)
							{
								struct MenuItem *MenuItem;

								while(Code != MENUNULL)
								{
									MenuItem = ItemAddress(PanelMenu,Code);

									switch((ULONG)MENU_USERDATA(MenuItem))
									{
										case 3:			Class = IDCMP_CLOSEWINDOW;
													break;

										case 1:			strcpy(Buffer,((struct StringInfo *)GadgetArray[0] -> SpecialInfo) -> Buffer);

													Success = TRUE;

													Terminated = TRUE;
													break;

										case 2:			GT_SetGadgetAttrs(GadgetArray[0],PanelWindow,NULL,
														GA_Disabled,TRUE,
													TAG_DONE);

													SetWait(PanelWindow);

													PanelWindow -> Flags |= WFLG_RMBTRAP;

													if(FileRequest = GetFile("Load File...","","",DummyBuffer,NULL,FALSE,FALSE))
													{
														GT_SetGadgetAttrs(GadgetArray[0],PanelWindow,NULL,
															GTST_String,DummyBuffer,
														TAG_DONE);

														RefreshGList(GadgetList,PanelWindow,NULL,(UWORD)-1);

														FreeAslRequest(FileRequest);
													}

													GT_SetGadgetAttrs(GadgetArray[0],PanelWindow,NULL,
														GA_Disabled,FALSE,
													TAG_DONE);

													ClearPointer(PanelWindow);

													PanelWindow -> Flags &= ~WFLG_RMBTRAP;
													break;
									}

									Code = MenuItem -> NextSelect;
								}

								if(ActiveGadget)
									ActivateGadget(ActiveGadget,PanelWindow,NULL);
							}

							if(Class == IDCMP_CLOSEWINDOW)
								Terminated = TRUE;

							if(Class == IDCMP_GADGETUP)
							{
								if(!DontActivate)
								{
									switch(Gadget -> GadgetID)
									{
										case 0:
										case 1:	strcpy(Buffer,((struct StringInfo *)GadgetArray[0] -> SpecialInfo) -> Buffer);

											Success = TRUE;

											Terminated = TRUE;
											break;

										case 2:	GT_SetGadgetAttrs(GadgetArray[0],PanelWindow,NULL,
														GA_Disabled,TRUE,
													TAG_DONE);
											SetWait(PanelWindow);

											PanelWindow -> Flags |= WFLG_RMBTRAP;

											if(FileRequest = GetFile("Load File...","","",DummyBuffer,NULL,FALSE,FALSE))
											{
												strcpy(((struct StringInfo *)GadgetArray[0] -> SpecialInfo) -> Buffer,DummyBuffer);

												RefreshGList(GadgetList,PanelWindow,NULL,(UWORD)-1);

												FreeAslRequest(FileRequest);
											}

											OnGadget(GadgetArray[0],PanelWindow,NULL);
											ClearPointer(PanelWindow);

											PanelWindow -> Flags &= ~WFLG_RMBTRAP;
											break;

										case 3:	Terminated = TRUE;
											break;
									}
								}
								else
									DontActivate = FALSE;
							}
						}
					}

					PanelWindow -> Flags |= WFLG_RMBTRAP;

					ClearMenuStrip(PanelWindow);

					RemoveGList(PanelWindow,GadgetList,(UWORD)-1);

					PopWindow();

					CloseWindow(PanelWindow);
				}

				FreeGadgets(GadgetList);
			}

			FreeMenus(PanelMenu);
		}
	}

	return(Success);
}

	/* WakeUp(struct Window *Window):
	 *
	 *	Pop a window to the front and alert the user.
	 */

VOID
WakeUp(struct Window *Window)
{
	if(Window)
		BumpWindow(Window);

	Beep();

	WaitTime(2,0);
/*
	Beep();

	WaitTime(0,MILLION / 4);

	Beep();

	WaitTime(0,MILLION / 4);*/
}

	/* SendAmigaDOSCommand(UBYTE *Name):
	 *
	 *	Let the current default Shell execute an AmigaDOS
	 *	command. Block until the command has returned.
	 */

VOID
SendAmigaDOSCommand(UBYTE *Name)
{
	BYTE DummyBuffer[2];
	BPTR File;

		/* Let the asynchronous Rexx server pick up and process
		 * all incoming messages rather than sending them to the
		 * main process (hopefully avoids deadlock situations).
		 */

	BatchMode = TRUE;

		/* Open the console window or whatever the user
		 * wants us to open here.
		 */

	if(File = Open(WindowName,MODE_NEWFILE))
	{
		ULONG		 Tags[3];
		struct ConUnit	*ConUnit = GetConUnit(((struct FileHandle *)BADDR(File)) -> fh_Type);

		Tags[0] = SYS_Output;
		Tags[1] = (ULONG)File;
		Tags[2] = TAG_DONE;

		if(ConUnit)
		{
			if(ConUnit -> cu_Window)
				BumpWindow(ConUnit -> cu_Window);
		}

			/* Make the Shell execute the command. */

/*		SystemTags(Name,SYS_Output,File,TAG_DONE);*/

		System(Name,(APTR)&Tags[0]);

			/* Wait for some impressive user action. */

		Write(File,"Press \33[1mRETURN\33[0m or close window to continue. ",51);

		Read(File,DummyBuffer,1);

		Close(File);
	}

		/* Bring the `term' main screen to the front and
		 * fall back into asynchronous Rexx message processing.
		 */

	BumpWindow(Window);

	BatchMode = FALSE;
}

	/* RexxBackgroundServer():
	 *
	 *	The background process to handle the rexx
	 *	massaging.
	 */

VOID __saveds
RexxBackgroundServer()
{
	BPTR		 OldCOS,OldCIS,NewCOS,NewCIS;
	struct MsgPort	*OldConsoleTask,*ReplyPort;

	BYTE		 DummyBuffer[2];

	struct RexxMsg	*RexxMsg;

	struct Process	*BackgroundProcess;
	struct Message	*SetupMsg;

		/* Look who we are. */

	BackgroundProcess = (struct Process *)SysBase -> ThisTask;

		/* Wait for startup message. */

	WaitPort(&BackgroundProcess -> pr_MsgPort);

		/* Pick the message up. */

	SetupMsg = GetMsg(&BackgroundProcess -> pr_MsgPort);

		/* Create a reply port the Rexx message is to
		 * return to after processing.
		 */

	if(ReplyPort = (struct MsgPort *)CreateMsgPort())
	{
			/* Remember previous I/O streams and
			 * console handler.
			 */

		OldCOS		= BackgroundProcess -> pr_COS;
		OldCIS		= BackgroundProcess -> pr_CIS;

		OldConsoleTask	= BackgroundProcess -> pr_ConsoleTask;

			/* The following lines perform an `almost illegal'
			 * action: new console I/O streams are opened for
			 * the `term' main process. This is due to a rather
			 * helpful Rexx server feature. Errors, messages
			 * and other data are sent to the current output
			 * stream.
			 */

		if(NewCIS = Open(WindowName,MODE_NEWFILE))
		{
			struct FileHandle	*FileHandle = (struct FileHandle *)BADDR(NewCIS);
			struct ConUnit		*ConUnit = GetConUnit(((struct FileHandle *)BADDR(NewCIS)) -> fh_Type);

				/* Lock until we're done with the forgery. */

			Forbid();

			BackgroundProcess -> pr_ConsoleTask	= (APTR)FileHandle -> fh_Type;

			BackgroundProcess -> pr_CIS		= NewCIS;
			BackgroundProcess -> pr_COS		= NewCOS = Open("*",MODE_NEWFILE);

			Permit();

			RexxWindow = NULL;

			if(ConUnit)
			{
				if(RexxWindow = ConUnit -> cu_Window)
					BumpWindow(RexxWindow);
			}

				/* Send the command and wait for a reply. */

			if(SendRexxCommand(ReplyPort,SetupMsg -> mn_Node . ln_Name,NULL,NULL))
			{
				ULONG SignalSet;

				SignalSet = Wait((1 << ReplyPort -> mp_SigBit) | SIGBREAKF_CTRL_C);

				if(SignalSet & SIGBREAKF_CTRL_C)
					Signal(ThisProcess,SIGBREAKF_CTRL_D);

				if(!(SignalSet & (1 << ReplyPort -> mp_SigBit)))
					Wait(1 << ReplyPort -> mp_SigBit);

					/* Pick up the RexxMsg (SendRexxCommand
					 * had allocated it for us) and
					 * examine the return codes.
					 */

				if(RexxMsg = (struct RexxMsg *)GetMsg(ReplyPort))
				{
					if(!ExitQuietly)
					{
						/* This doesn't look too
						 * good, does it?
						 */

						if(RexxMsg -> rm_Result1)
							FPrintf(NewCIS,"\nCommand \"%s\" has terminated with code %ld, %ld.\n",SetupMsg -> mn_Node . ln_Name,RexxMsg -> rm_Result1,RexxMsg -> rm_Result2);

						/* Show our hand and return
						 * to the usual business.
						 */

						Write(BackgroundProcess -> pr_COS,"Press \33[1mRETURN\33[0m or close window to continue. ",51);

						Rexx2Front();

						Read(NewCIS,DummyBuffer,1);

						Term2Front();
					}
					else
						ExitQuietly = FALSE;

						/* Release the message. */

					FreeRexxCommand(RexxMsg);
				}
			}

			Forbid();

			RexxWindow = NULL;

			Permit();

			BumpWindow(Window);

				/* Close our fake I/O streams. */

			Forbid();

			Close(NewCIS);
			Close(NewCOS);

				/* And install the previous pointers. */

			BackgroundProcess -> pr_ConsoleTask	= (APTR)OldConsoleTask;

			BackgroundProcess -> pr_CIS		= OldCIS;
			BackgroundProcess -> pr_COS		= OldCOS;

			Permit();
		}

			/* Remove the reply port. */

		DeleteMsgPort(ReplyPort);
	}

		/* We are done, lock and reply the message causing the
		 * main process to return to the input loop.
		 */

	Forbid();

	ReplyMsg(SetupMsg);
}

	/* SendARexxCommand(UBYTE *Name):
	 *
	 *	Let the ARexx server execute a command (or a script
	 *	file if necessary) and block until the command
	 *	has returned.
	 */

VOID
SendARexxCommand(UBYTE *Name)
{
	struct Process	*BackgroundProcess;
	struct Message	*SetupMsg;
	struct MsgPort	*ReplyPort;

	ULONG		 SignalSet;

		/* Create a reply port for the info message. */

	if(ReplyPort = CreateMsgPort())
	{
			/* Allocate the message body. */

		if(SetupMsg = (struct Message *)AllocVec(sizeof(struct Message),MEMF_PUBLIC|MEMF_CLEAR))
		{
				/* Set up the message itself. */

			SetupMsg -> mn_Node . ln_Name	= Name;
			SetupMsg -> mn_ReplyPort	= ReplyPort;
			SetupMsg -> mn_Length		= sizeof(struct Message);

				/* Create the background process which will
				 * handle all the messy rexx message sending
				 * for us.
				 */

			if(BackgroundProcess = (struct Process *)CreateNewProcTags(
				NP_Entry,	RexxBackgroundServer,
				NP_Name,	"term Rexx Background Process",
				NP_StackSize,	16384,
			TAG_END))
			{
				SetSignal(0,SIGBREAKF_CTRL_D);

					/* Send the startup message. */

				PutMsg(&BackgroundProcess -> pr_MsgPort,SetupMsg);

					/* Go into loop and wait for the
					 * background process to return.
					 */

				FOREVER
				{
					SignalSet = Wait(SIG_REXX | (1 << ReplyPort -> mp_SigBit));

						/* Yet another rexx message. */

					if(SignalSet & SIG_REXX)
						HandleRexx();

						/* The background server has
						 * finished.
						 */

					if(SignalSet & (1 << ReplyPort -> mp_SigBit))
						break;
				}

				SetSignal(0,SIGBREAKF_CTRL_D);
			}

				/* Free the message. */

			FreeVec(SetupMsg);
		}

			/* Delete the reply port. */

		DeleteMsgPort(ReplyPort);
	}
}

	/* ahtoi(UBYTE *String):
	 *
	 *	Turn a hexadecimal string into an integer (borrowed from
	 *	Matt Dillon's dmouse.c).
	 */

LONG
ahtoi(UBYTE *String)
{
	LONG Value = 0;
	UBYTE c;

	while(c = *String)
	{
		Value <<= 4;

		if(c >= '0' && c <= '9')
			Value |= (c & 15);
		else
			Value |= (c & 15) + 9;

		++String;
	}

	return(Value);
}

	/* BlockWindows():
	 *
	 *	Block the main window and the status window (i.e. disable
	 *	the menu and attach a wait pointer).
	 */

VOID
BlockWindows()
{
	if(!(BlockNestCount++))
	{
		SetWait(Window);
		SetWait(StatusWindow);

		if(PacketWindow)
		{
			SetWait(PacketWindow);

			PacketWindow -> Flags |= WFLG_RMBTRAP;

			GT_SetGadgetAttrs(PacketGadgetArray[0],PacketWindow,NULL,
				GA_Disabled,TRUE,
			TAG_DONE);
		}

		Window		-> Flags |= WFLG_RMBTRAP;
		StatusWindow	-> Flags |= WFLG_RMBTRAP;

		WeAreBlocking = TRUE;
	}
}

	/* ReleaseWindows():
	 *
	 *	Reenable the menus and clear the wait pointer.
	 */

VOID
ReleaseWindows()
{
	if(!(--BlockNestCount))
	{
		Window		-> Flags &= ~WFLG_RMBTRAP;
		StatusWindow	-> Flags &= ~WFLG_RMBTRAP;

		ClearPointer(Window);
		ClearPointer(StatusWindow);

		if(PacketWindow)
		{
			PacketWindow -> Flags &= ~WFLG_RMBTRAP;

			ClearPointer(PacketWindow);

			OnGadget(PacketGadgetArray[0],PacketWindow,NULL);
		}

		FlushMsg(Window);

		WeAreBlocking = FALSE;
	}
}

	/* LineRead(BPTR File,UBYTE *Buffer,LONG MaxChars):
	 *
	 *	Read a few bytes from a file (à la gets).
	 */

BYTE
LineRead(BPTR File,UBYTE *Buffer,LONG MaxChars)
{
	STATIC UBYTE Data[1024];
	STATIC LONG RIdx = 0,RLen = 0;

	LONG i;

	if(File)
	{
		for(i = 0 ; i < MaxChars ; i++)
		{
			if(RIdx >= RLen)
			{
				RLen = Read(File,Data,1024);

				RIdx = 0;

				if(RLen <= 0)
				{
					Buffer[i] = 0;
					return(FALSE);
				}
			}

			if((Buffer[i] = Data[RIdx]) != '\r')
			{
				if(Data[RIdx++] == '\n')
				{
					Buffer[i + 1] = 0;
					break;
				}
			}
		}
	}
	else
		RIdx = RLen = 0;

	return(TRUE);
}

	/* FlowInit():
	 *
	 *	Set up the data flow parser. The parser scans the serial
	 *	output data for more or less interesting modem output
	 *	(carrier lost, connect, etc.).
	 */

VOID
FlowInit()
{
	FlowInfo . Changed = FALSE;

	FlowInfo . NoCarrier = FlowInfo . ZModemUpload = FlowInfo . Connect = FlowInfo . Voice = FlowInfo . Ring = FlowInfo . Busy = FALSE;

	AttentionBuffers[0] = Config . NoCarrier;
	AttentionBuffers[1] = Config . Ring;
	AttentionBuffers[2] = Config . Voice;
	AttentionBuffers[3] = "*B01";
	AttentionBuffers[4] = "**B01";
	AttentionBuffers[5] = Config . Connect;
	AttentionBuffers[6] = Config . Busy;

	FlowCount = 0;

	memset(&AttentionCount[0],0,7);

	BaudPending = FALSE;

	FullCheck = FALSE;
}

	/* FlowFilter(UBYTE Char):
	 *
	 *	Send a character through the data flow parser and look
	 *	if it's part of a modem message.
	 */

VOID
FlowFilter(UBYTE Char)
{
	STATIC BYTE IgnoreChar;
	BYTE i,Matches = 0,Start,End,WasOnline = Online;

		/* Full data check is a lot slower than looking for
		 * just a single sequence (such as the `CONNECT'
		 * below). This mode is reserved for the dial panel.
		 */

	if(FullCheck)
	{
		Start	= 1;
		End	= 7;
	}
	else
	{
		Start	= 0;

		if(UsesZModem)
			End = 5;
		else
			End = 3;
	}

		/* We already got a `CONNECT' and the
		 * `connect auto-baud' feature is enabled.
		 * Continue scanning the serial output
		 * data for the actual baud rate.
		 */

	if(BaudPending)
	{
		if(Char >= '0' && Char <= '9')
		{
			BaudBuffer[BaudCount++] = Char;

			if(IgnoreChar)
				IgnoreChar = 0;
		}
		else
		{
				/* The scanner has found a
				 * non-numerical character.
				 * This is either a blank
				 * space or something else.
				 * The latter tells us
				 * that the baud rate has
				 * been identified and is
				 * ready to be used.
				 */

			if(Char != IgnoreChar)
			{
				BaudBuffer[BaudCount] = 0;

				BaudPending = FALSE;
			}
		}
	}

	if(!BaudPending)
	{
			/* Scan all ID strings for matches. */

		for(i = Start ; i < End ; i++)
		{
				/* This sequence is a probable
				 * match.
				 */

			if(AttentionCount[i] == FlowCount)
			{
					/* Does the character
					 * fit into the sequence?
					 */

				if(Char == AttentionBuffers[i][FlowCount])
				{
						/* Increment the
						 * number of matching
						 * characters in this
						 * sequence.
						 */

					AttentionCount[i]++;

						/* Did we hit the
						 * last character
						 * in the sequence?
						 */

					if(AttentionBuffers[i][FlowCount + 1])
						Matches++;
					else
					{
						Matches = 0;

							/* We've got a valid
							 * sequence, now look
							 * which flags to change.
							 */

						switch(i)
						{
								/* We got a `no carrier' message. */

							case 0:	if(!FlowInfo . NoCarrier)
								{
									FlowInfo . NoCarrier	= TRUE;
									FlowInfo . Changed	= TRUE;
								}

								Online = FALSE;

									/* Clear the password. */

								Password[0] = 0;

								if(WasOnline)
								{
									if(CurrentPay)
										LogAction("Carrier lost (cost %ld.%02ld).",CurrentPay / 100,CurrentPay % 100);
									else
										LogAction("Carrier lost.");

									Say("Carrier lost.");
								}
								break;

								/* Got a voice call. */

							case 1:	if(!FlowInfo . Voice)
								{
									FlowInfo . Voice	= TRUE;
									FlowInfo . Changed	= TRUE;
								}
								break;

								/* Got another call. */

							case 2:	if(!FlowInfo . Ring)
								{
									FlowInfo . Ring		= TRUE;
									FlowInfo . Changed	= TRUE;
								}
								break;

							case 3:
							case 4:	if(!FlowInfo . ZModemUpload)
								{
									FlowInfo . ZModemUpload	= TRUE;
									FlowInfo . Changed	= TRUE;
								}
								break;

								/* Got a connect message. */

							case 5:	if(!FlowInfo . Connect)
								{
									FlowInfo . Connect	= TRUE;
									FlowInfo . Changed	= TRUE;
								}

								if(Config . ConnectAutoBaud)
								{
									BaudBuffer[0] = 0;

									BaudPending = TRUE;
									BaudCount = 0;

									IgnoreChar = ' ';
								}

								break;

								/* Line is busy. */

							case 6:	if(!FlowInfo . Busy)
								{
									FlowInfo . Busy		= TRUE;
									FlowInfo . Changed	= TRUE;
								}
								break;
						}
					}
				}
			}
		}

			/* We've got a good match (recognized
			 * a sequence, so reset the data flow
			 * scanner.
			 */

		if(!Matches)
		{
			if(FlowCount)
			{
				FlowCount = 0;

				memset(&AttentionCount[0],0,7);
			}
		}
		else
			FlowCount++;
	}
	else
	{
			/* This checks for just a single sequence:
			 * the notorious `NO CARRIER'.
			 */

		if(AttentionCount[0] == FlowCount)
		{
			if(Char == AttentionBuffers[0][FlowCount])
			{
				AttentionCount[0]++;

				if(AttentionBuffers[0][FlowCount + 1])
					Matches++;
				else
				{
					Matches = 0;

					if(!FlowInfo . NoCarrier)
					{
						FlowInfo . NoCarrier	= TRUE;
						FlowInfo . Changed	= TRUE;
					}

					Online = FALSE;

						/* Clear the password. */

					Password[0] = 0;

					if(WasOnline)
					{
						if(CurrentPay)
							LogAction("Carrier lost (cost %ld.%02ld).",CurrentPay / 100,CurrentPay % 100);
						else
							LogAction("Carrier lost.");

						Say("Carrier lost.");
					}
				}
			}
		}

		if(!Matches)
		{
			if(FlowCount)
			{
				FlowCount = 0;

				memset(&AttentionCount[0],0,7);
			}
		}
		else
			FlowCount++;
	}
}

	/* LoadMacros(UBYTE *Name,struct MacroKeys *Keys):
	 *
	 *	Load the keyboard macros from a file.
	 */

BYTE
LoadMacros(UBYTE *Name,struct MacroKeys *Keys)
{
	struct IFFHandle	*Handle;
	BYTE			 Success = FALSE;
	struct StoredProperty	*Prop;
	struct TermInfo		*TermInfo;

	if(Handle = AllocIFF())
	{
		if(Handle -> iff_Stream = Open(Name,MODE_OLDFILE))
		{
			InitIFFasDOS(Handle);

			if(!OpenIFF(Handle,IFFF_READ))
			{
				/* Collect version number ID if
				 * available.
				 */

				if(!PropChunks(Handle,&VersionProps[0],1))
				{
					/* The following line tells iffparse to stop at the
					 * very beginning of a `Type' chunk contained in a
					 * `TERM' FORM chunk.
					 */

					if(!StopChunk(Handle,'TERM','KEYS'))
					{
						/* Parse the file... */

						if(!ParseIFF(Handle,IFFPARSE_SCAN))
						{
							/* Did we get a version ID? */

							if(Prop = FindProp(Handle,'TERM','VERS'))
							{
								TermInfo = (struct TermInfo *)Prop -> sp_Data;

								/* Is it the file format we are able
								 * to read?
								 */

								if(TermInfo -> Version == TermVersion && TermInfo -> Revision <= TermRevision && TermInfo -> Revision >= 6)
								{
									/* The file read pointer is positioned
									 * just in front of the first data
									 * to be read, so let's don't disappoint
									 * iffparse and read it.
									 */

									if(ReadChunkBytes(Handle,Keys,sizeof(struct MacroKeys)) == sizeof(struct MacroKeys))
										Success = TRUE;
								}
								else
								{
										/* Probably an older revision. */

									if(TermInfo -> Version == 1 && TermInfo -> Revision < 6)
									{
										memset(Keys,0,sizeof(struct MacroKeys));

										if(ReadChunkBytes(Handle,Keys,10 * 256) == 10 * 256)
											Success = TRUE;
									}
								}
							}
							else
							{
									/* File was created by WriteIFFData previous
									 * to revision 1.4.
									 */

								memset(Keys,0,sizeof(struct MacroKeys));

								if(ReadChunkBytes(Handle,Keys,10 * 256) == 10 * 256)
									Success = TRUE;
							}
						}
					}
				}

				CloseIFF(Handle);
			}

			Close(Handle -> iff_Stream);
		}

		FreeIFF(Handle);
	}

	return(Success);
}

	/* FindThisItem(ULONG MenuID):
	 *
	 *	Scan the main menu for a menuitem associated with a
	 *	menu ID.
	 */

struct MenuItem *
FindThisItem(ULONG MenuID)
{
	struct Menu	*FirstMenu;
	struct MenuItem	*FirstItem;

	for(FirstMenu = Menu ; FirstMenu -> NextMenu ; FirstMenu = FirstMenu -> NextMenu)
	{
		for(FirstItem = FirstMenu -> FirstItem ; FirstItem -> NextItem ; FirstItem = FirstItem -> NextItem)
		{
			if((ULONG)GTMENUITEM_USERDATA(FirstItem) == MenuID)
				return(FirstItem);
		}
	}

	return(NULL);
}

	/* GetFileSize(UBYTE *Name):
	 *
	 *	Simple routine to return the size of a file in
	 *	bytes.
	 */

LONG
GetFileSize(UBYTE *Name)
{
	struct FileInfoBlock	*FileInfo;
	BPTR			 FileLock;
	LONG			 FileSize = 0;

	if(FileInfo = (struct FileInfoBlock *)AllocMem(sizeof(struct FileInfoBlock),MEMF_PUBLIC))
	{
		if(FileLock = Lock(Name,ACCESS_READ))
		{
			if(Examine(FileLock,FileInfo))
				FileSize = FileInfo -> fib_Size;

			UnLock(FileLock);
		}

		FreeMem(FileInfo,sizeof(struct FileInfoBlock));
	}

	return(FileSize);
}

	/* StrCmp(UBYTE *a,UBYTE *b):
	 *
	 *	Perform case insensitive string comparison. I know
	 *	that Stricmp in utility.library handles the same
	 *	job, but by the time I had spread calls to this
	 *	routine across a number of modules I lost interest
	 *	in changing the call.
	 */

LONG
StrCmp(UBYTE *a,UBYTE *b)
{
	for( ; Local2Upper(*a) == Local2Upper(*b) ; a++, b++)
	{
		if(!(*a))
			return(0);
	}

	return((LONG)(Local2Upper(*a) - Local2Upper(*b)));
}

	/* GetFile(UBYTE *Title,UBYTE *Directory,UBYTE *Name,UBYTE *Buffer,UBYTE *Pattern,BYTE SaveFlag,BYTE MultiSelect):
	 *
	 *	Call the asl.library file requester to select a single or
	 *	a couple of files.
	 */

struct FileRequester *
GetFile(UBYTE *Title,UBYTE *Directory,UBYTE *Name,UBYTE *Buffer,UBYTE *Pattern,BYTE SaveFlag,BYTE MultiSelect)
{
	struct FileRequester	*AslFileRequest;
	BYTE			 Result = FALSE;
	LONG			 Flags;

	STATIC UBYTE		 DirBuffer[256];

		/* We use this tag array to remember the size and
		 * position of the asl requester window.
		 */

	STATIC struct {
		ULONG Tag1,	LeftEdge;
		ULONG Tag2,	TopEdge;
		ULONG Tag3,	Width;
		ULONG Tag4,	Height;
		ULONG Tag5;
	} Dims = {
		ASL_LeftEdge,	0,
		ASL_TopEdge,	0,
		ASL_Width,	0,
		ASL_Height,	0,
		TAG_DONE
	};

		/* Empty directory string? Revert to the last directory
		 * name.
		 */

	if(!Directory[0])
		Directory = DirBuffer;

		/* If a wildcard pattern is required, add a gadget
		 * to display it.
		 */

	if(Pattern)
	{
		Flags = FILF_PATGAD;

		if(!Pattern[0])
			Pattern = "#?";
	}
	else
	{
		Flags = 0;

		Pattern = "#?";
	}

		/* Set the save flag if we are about to save something. */

	if(SaveFlag)
		Flags |= FILF_SAVE;

		/* Set the multiselect bit if multiple files are
		 * to be selected (e.g. for batch file upload).
		 */

	if(MultiSelect)
		Flags |= FILF_MULTISELECT;

		/* Allocate the asl.library directory requester
		 * and display it.
		 */

	if(AslFileRequest = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
		ASL_Window,	Window,
		ASL_File,	Name,
		ASL_Dir,	Directory,
		ASL_Hail,	Title,
		ASL_FuncFlags,	Flags,
		ASL_Pattern,	Pattern,
		ASL_OKText,	SaveFlag ? "Save" : "Open",
	Dims . Width ? TAG_MORE : TAG_END,&Dims))
	{
		if(AslRequestTags(AslFileRequest,TAG_DONE))
		{
			Dims . LeftEdge	= AslFileRequest -> rf_LeftEdge;
			Dims . TopEdge	= AslFileRequest -> rf_TopEdge;
			Dims . Width	= AslFileRequest -> rf_Width;
			Dims . Height	= AslFileRequest -> rf_Height;

				/* Do we have a valid file name? */

			if(AslFileRequest -> rf_File[0])
			{
					/* Build a legal path/file string. */

				strcpy(Buffer,AslFileRequest -> rf_Dir);

				AddPart((UBYTE *)Buffer,(UBYTE *)AslFileRequest -> rf_File,256);

				Result = TRUE;

				strcpy(DirBuffer,AslFileRequest -> rf_Dir);
			}
		}
	}

		/* We didn't get a file, no need to keep the
		 * file requester.
		 */

	if(!Result && AslFileRequest)
	{
		FreeAslRequest(AslFileRequest);
		return(NULL);
	}
	else
		return(AslFileRequest);
}

	/* MyEasyRequest(struct Window *Window,UBYTE *Text,UBYTE *Gadgets,...):
	 *
	 *	Really quite simple varargs version of Intuition's
	 *	EasyRequest requester.
	 */

SHORT __stdargs
MyEasyRequest(struct Window *Window,UBYTE *Text,UBYTE *Gadgets,...)
{
	struct EasyStruct	Easy;
	SHORT			Result;
	ULONG			IDCMP = NULL;
	va_list	 		VarArgs;

		/* Standard data. */

	Easy . es_StructSize	= sizeof(struct EasyStruct);
	Easy . es_Flags		= NULL;
	Easy . es_Title		= (UBYTE *)"term Request";
	Easy . es_TextFormat	= (UBYTE *)Text;
	Easy . es_GadgetFormat	= (UBYTE *)Gadgets;

		/* Use the argument array to build the
		 * requester and display it.
		 */

	va_start(VarArgs,Gadgets);
	Result = EasyRequestArgs(Window,&Easy,&IDCMP,VarArgs);
	va_end(VarArgs);

	return(Result);
}

	/* A handy positioning macro. */

#define MoveItem(X,Y) Move(RPort,((11 + X * 20) * 8) + ((Screen -> Width - 640) >> 1),2 + 6 + Y * 8)

	/* StatusServer():
	 *
	 *	Asynchronous task to continuosly display the current
	 *	terminal settings.
	 */

VOID __saveds
StatusServer()
{
	struct RastPort		*RPort = StatusWindow -> RPort;
	UBYTE			 Buffer[20];

	struct timerequest	*TimeRequest;
	struct MsgPort		*TimePort;

	struct timeval		 OnlineTime;
	BYTE			 GotOnline = FALSE;

	BYTE			 KeepGoing = TRUE;

	BYTE			 LastFont = -1;
	BYTE			 LastEmulation = -1;

	BYTE			 LastProtocol[40],ProtocolBuffer[10],i;

	LONG			 LastBaud = -1;
	BYTE			 LastBitsPerChar = -1;
	BYTE			 LastParity = -1;
	BYTE			 LastStopBits = -1;

	BYTE			 LastStatus = -1;

	LONG			 SecCount;
	BYTE			 FlagBit;

	LONG			 ThisHour,ThisMinute,TestTime;

	BYTE			 FlashIt = FALSE;

	LastProtocol[0] = 0;

		/* Create a timer device request. */

	if(TimePort = (struct MsgPort *)CreateMsgPort())
	{
		if(TimeRequest = (struct timerequest *)CreateIORequest(TimePort,sizeof(struct timerequest)))
		{
			if(!OpenDevice(TIMERNAME,UNIT_VBLANK,TimeRequest,0))
			{
					/* Signal our father process
					 * that we're running.
					 */

				Signal(ThisProcess,SIGBREAKF_CTRL_C);

				MoveItem(3,1);
				Print("00:00:00");

					/* Keep on displaying. */

				while(KeepGoing)
				{
						/* Are we to quit? */

					if(SetSignal(0,0) & SIGBREAKF_CTRL_C)
					{
						KeepGoing = FALSE;

						SetSignal(0,SIGBREAKF_CTRL_C);
					}

						/* Get the current time. */

					TimeRequest -> tr_node . io_Command = TR_GETSYSTIME;
					DoIO(TimeRequest);

						/* A connection has just
						 * been established.
						 */

					if(Online && !GotOnline)
					{
						OnlineTime = TimeRequest -> tr_time;

						GotOnline = TRUE;

						SecCount = 0;

						FlagBit = FALSE;
					}

						/* Print the current time. */

					ThisHour	= (TimeRequest -> tr_time . tv_secs % 86400) / 3600;
					ThisMinute	= (TimeRequest -> tr_time . tv_secs % 3600) / 60;

					SPrintf(Buffer,"%02ld:%02ld:%02ld",ThisHour,ThisMinute,TimeRequest -> tr_time . tv_secs % 60);

					MoveItem(3,0);
					Print(Buffer);

					if(Online)
					{
						struct timeval TempTime;

						if(PayPerUnit[0] || PayPerUnit[1])
						{
							TestTime = ThisHour * 6 + ThisMinute / 10;

							if(TestTime >= TimeOfDay[0] && TestTime <= TimeOfDay[1])
								PreferredTime = 0;
							else
							{
								if(TestTime >= TimeOfDay[1] && TestTime <= TimeOfDay[0])
									PreferredTime = 1;
								else
								{
									if(TestTime >= TimeOfDay[0] && TestTime >= TimeOfDay[1])
									{
										if(TimeOfDay[0] > TimeOfDay[1])
											PreferredTime = 0;
										else
											PreferredTime = 1;
									}
									else
									{
										if(TestTime <= TimeOfDay[0] && TestTime <= TimeOfDay[1])
										{
											if(TimeOfDay[0] > TimeOfDay[1])
												PreferredTime = 0;
											else
												PreferredTime = 1;
										}
									}
								}
							}

							if(!CurrentPay)
								CurrentPay = PayPerUnit[PreferredTime];

							FlagBit ^= TRUE;

							if(!FlagBit)
							{
								if((SecCount++) == SecPerUnit[PreferredTime])
								{
									SecCount = 0;

									CurrentPay += PayPerUnit[PreferredTime];
								}
							}
						}

							/* Show the time
							 * we have been online
							 * yet.
							 */

						TempTime = TimeRequest -> tr_time;

						SubTime(&TempTime,&OnlineTime);

						SPrintf(Buffer,"%02ld:%02ld:%02ld",(TempTime . tv_secs % 86400) / 3600,(TempTime . tv_secs % 3600) / 60,TempTime . tv_secs % 60);

						MoveItem(3,1);
						
						Print(Buffer);
					}
					else
					{
						if(GotOnline)
							GotOnline = FALSE;
					}

						/* Display the current terminal
						 * status.
						 */

					if(LastStatus != Status)
					{
						LastStatus = Status;

						MoveItem(0,0);
						Print(ConfigStatus[LastStatus]);
					}

						/* Show the current transfer
						 * protocol.
						 */

					if(strcmp(LastProtocol,FilePart(Config . Protocol)))
					{
						strcpy(LastProtocol,FilePart(Config . Protocol));

						strcpy(ProtocolBuffer,"        ");

						for(i = 0 ; i < 8 ; i++)
						{
							if(!LastProtocol[i + 3] || LastProtocol[i + 3] == '.')
								break;
							else
								ProtocolBuffer[i] = LastProtocol[i + 3];
						}

						MoveItem(1,0);
						Print(ProtocolBuffer);
					}

						/* Show the current baud
						 * rate.
						 */

					if(LastBaud != Config . BaudRate)
					{
						LastBaud = Config . BaudRate;

						SPrintf(Buffer,"%ld        ",LastBaud);

						Buffer[8] = 0;

						MoveItem(2,0);
						Print(Buffer);
					}

						/* Show the current
						 * terminal font.
						 */

					if(LastFont != Config . Font)
					{
						LastFont = Config . Font;

						MoveItem(0,1);
						Print(ConfigFont[LastFont]);
					}

						/* Show the current terminal
						 * emulation.
						 */

					if(LastEmulation != Config . Emulation)
					{
						LastEmulation = Config . Emulation;

						MoveItem(1,1);
						Print(ConfigEmulation[LastEmulation]);
					}

						/* Show the current serial
						 * parameters (parity, etc).
						 */

					if(LastBitsPerChar != Config . BitsPerChar || LastParity != Config . Parity || LastStopBits != Config . StopBits)
					{
						LastBitsPerChar	= Config . BitsPerChar;
						LastParity	= Config . Parity;
						LastStopBits	= Config . StopBits;

						SPrintf(Buffer,"%ld-%s-%ld",LastBitsPerChar,ConfigParity[LastParity],LastStopBits);

						MoveItem(2,1);
						Print(Buffer);
					}

						/* Wait another half a second. */

					if(KeepGoing)
					{
						TimeRequest -> tr_node . io_Command	= TR_ADDREQUEST;
						TimeRequest -> tr_time . tv_secs	= 0;
						TimeRequest -> tr_time . tv_micro	= MILLION / 2;

						DoIO(TimeRequest);
					}

						/* Make the colours blink. */

					if((Window -> Flags & WFLG_MENUSTATE) || (Window -> Pointer))
					{
						if(!FlashIt)
						{
							if(Screen == IntuitionBase -> FirstScreen)
								LoadRGB4(VPort,Config . Colours,16);

							FlashIt = TRUE;
						}
					}
					else
					{
							/* Are we to flash the display? */

						if(!Config . DisableBlinking)
						{
							if(Screen == IntuitionBase -> FirstScreen)
							{
								if(FlashIt)
									LoadRGB4(VPort,BlinkColours,16);
								else
									LoadRGB4(VPort,Config . Colours,16);
							}

							FlashIt ^= TRUE;
						}
					}
				}

				CloseDevice(TimeRequest);
			}

			DeleteIORequest(TimeRequest);
		}

		DeleteMsgPort(TimePort);
	}

		/* Signal the father process that we're done
		 * and quietly remove ourselves.
		 */

	Forbid();

	Signal(ThisProcess,SIGBREAKF_CTRL_C);

	StatusTask = NULL;

	RemTask(SysBase -> ThisTask);
}

	/* CloseWindowSafely(struct Window *Window):
	 *
	 *	Close a window freeing all messages pending at
	 *	its user port (taken from example source code
	 *	published once upon a time in Amiga Mail).
	 */

VOID
CloseWindowSafely(struct Window *Window)
{
	struct IntuiMessage	*IntuiMessage;
	struct Node		*Successor;

	Forbid();

	IntuiMessage = (struct IntuiMessage *)Window -> UserPort -> mp_MsgList . lh_Head;

	while(Successor = IntuiMessage -> ExecMessage . mn_Node . ln_Succ)
	{
		if(IntuiMessage -> IDCMPWindow == Window)
		{
			Remove(IntuiMessage);

			ReplyMsg((struct Message *)IntuiMessage);
		}

		IntuiMessage = (struct IntuiMessage *)Successor;
	}

	Window -> UserPort = NULL;

	ModifyIDCMP(Window,NULL);
	Permit();

	CloseWindow(Window);
}

	/* WaitTime(LONG Secs,LONG Micros):
	 *
	 *	Wait a given period of time.
	 */

VOID
WaitTime(LONG Secs,LONG Micros)
{
	TimeRequest -> tr_node . io_Command	= TR_ADDREQUEST;
	TimeRequest -> tr_time . tv_secs	= Secs;
	TimeRequest -> tr_time . tv_micro	= Micros;

	DoIO(TimeRequest);
}

	/* GetEnvDOS(UBYTE *Name,UBYTE *Buffer):
	 *
	 *	Get the contents of a vanilla AmigaDOS environment
	 *	variable.
	 */

UBYTE *
GetEnvDOS(UBYTE *Name,UBYTE *Buffer)
{
	LONG	Size;
	BPTR	File,SomeLock;

	Buffer[0] = 0;

		/* Is ENV: present? */

	if(SomeLock = Lock("ENV:",ACCESS_READ))
	{
		UBYTE SomeBuffer[80];

		UnLock(SomeLock);

		strcpy(SomeBuffer,"ENV:");
		strcat(SomeBuffer,Name);

			/* Open the file. */

		if(File = Open(SomeBuffer,MODE_OLDFILE))
		{
				/* Read the contents. */

			Size = Read(File,Buffer,256);

			Close(File);

			if(Size > 0)
			{
				Buffer[Size] = 0;

				return(Buffer);
			}
		}
	}

	return(NULL);
}

	/* SetEnvDOS(UBYTE *Name,UBYTE *Value):
	 *
	 *	Set the contents of a vanilla AmigaDOS environment
	 *	variable.
	 */

BYTE
SetEnvDOS(UBYTE *Name,UBYTE *Value)
{
	UBYTE	Buffer[80],*Destination;
	LONG	Length = 0;
	BPTR	File,FileLock;
	BYTE	Success = FALSE;
	SHORT	i;

	for(i = 0 ; i < 2 ; i++)
	{
		if(i)
			Destination = "ENVARC:";
		else
			Destination = "ENV:";

			/* Is ENV:/ENVARC: present? */
	
		if(FileLock = Lock(Destination,ACCESS_READ))
		{
			UnLock(FileLock);

			strcpy(Buffer,Destination);
			strcat(Buffer,Name);

				/* There already is a variable of that
				 * name in the environment storage
				 * directory.
				 */

			if(FileLock = Lock(Buffer,ACCESS_WRITE))
			{
				UnLock(FileLock);

					/* Delete the variable. */

				if(!DeleteFile(Buffer))
				{
					Success = FALSE;
					continue;
				}
			}

				/* Set the new variable. */

			if(Length = strlen(Value))
			{
				if(File = Open(Buffer,MODE_NEWFILE))
				{
					if(Write(File,Value,Length) != Length)
					{
						Close(File);
						DeleteFile(Buffer);

						Success = FALSE;
					}
					else
					{
						Close(File);
						SetProtection(Buffer,FIBF_EXECUTE);

						Success = TRUE;
					}
				}
				else
					Success = FALSE;
			}
			else
				Success = TRUE;
		}
		else
			Success = FALSE;
	}

	return(Success);
}

	/* BumpWindow(struct Window *SomeWindow):
	 *
	 *	Bring a window to the front (and shift the screen
	 *	back to its initial position).
	 */

VOID
BumpWindow(struct Window *SomeWindow)
{
	MoveScreen(SomeWindow -> WScreen,-SomeWindow -> WScreen -> LeftEdge,-SomeWindow -> WScreen -> TopEdge);

	ScreenToFront(SomeWindow -> WScreen);

	ActivateWindow(SomeWindow);
}

	/* BumpDefault():
	 *
	 *	Bring the current default screen to the front.
	 */

VOID
BumpDefault()
{
	struct Screen *DefaultScreen;

	if(DefaultScreen = (struct Screen *)LockPubScreen(NULL))
	{
		MoveScreen(DefaultScreen,-DefaultScreen -> LeftEdge,-DefaultScreen -> TopEdge);

		ScreenToFront(DefaultScreen);

		UnlockPubScreen(NULL,DefaultScreen);
	}
}

	/* WriteIFFData(UBYTE *Name,APTR Data,LONG Size,ULONG Type):
	 *
	 *	Write data to an IFF file (via iffparse.library).
	 */

BYTE
WriteIFFData(UBYTE *Name,APTR Data,LONG Size,ULONG Type)
{
	struct IFFHandle	*Handle;
	BYTE			 Success = FALSE;

		/* Allocate a handle. */

	if(Handle = AllocIFF())
	{
			/* Open an output stream. */

		if(Handle -> iff_Stream = Open(Name,MODE_NEWFILE))
		{
				/* Tell iffparse that this is
				 * a DOS handle.
				 */

			InitIFFasDOS(Handle);

				/* Open the handle for writing. */

			if(!OpenIFF(Handle,IFFF_WRITE))
			{
					/* Push outmost chunk onto stack. */

				if(!PushChunk(Handle,'TERM','FORM',IFFSIZE_UNKNOWN))
				{
						/* Add a version identifier. */

					if(!PushChunk(Handle,0,'VERS',IFFSIZE_UNKNOWN))
					{
						struct TermInfo TermInfo;

						TermInfo . Version	= TermVersion;
						TermInfo . Revision	= TermRevision;

							/* Write the version data. */

						if(WriteChunkBytes(Handle,&TermInfo,sizeof(struct TermInfo)) == sizeof(struct TermInfo))
						{
								/* Pop the version chunk, i.e. write it to the file. */

							if(PopChunk(Handle))
								Success = FALSE;
							else
							{
									/* Push the real data chunk on the stack. */

								if(!PushChunk(Handle,0,Type,IFFSIZE_UNKNOWN))
								{
										/* Write the data. */

									if(WriteChunkBytes(Handle,Data,Size) == Size)
										Success = TRUE;

											/* Pop the data chunk. */

									if(PopChunk(Handle))
										Success = FALSE;
								}
								else
									Success = FALSE;
							}
						}
						else
							Success = FALSE;
					}

						/* Seems that we're done, now try to pop the FORM chunk
						 * and return.
						 */

					if(PopChunk(Handle))
						Success = FALSE;
				}

					/* Close the handle (flush any pending data). */

				CloseIFF(Handle);
			}

				/* Close the DOS handle itself. */

			Close(Handle -> iff_Stream);
		}

			/* And free the IFF handle. */

		FreeIFF(Handle);
	}

	if(Success)
		SetProtection(Name,FIBF_EXECUTE);

	return(Success);
}

	/* ReadIFFData(UBYTE *Name,APTR Data,LONG Size,ULONG Type):
	 *
	 *	Read data from a `TERM' FORM chunk contained in an IFF file.
	 */

BYTE
ReadIFFData(UBYTE *Name,APTR Data,LONG Size,ULONG Type)
{
	struct IFFHandle	*Handle;
	BYTE			 Success = FALSE;
	struct StoredProperty	*Prop;
	struct TermInfo		*TermInfo;

	if(Handle = AllocIFF())
	{
		if(Handle -> iff_Stream = Open(Name,MODE_OLDFILE))
		{
			InitIFFasDOS(Handle);

			if(!OpenIFF(Handle,IFFF_READ))
			{
				/* Collect version number ID if
				 * available.
				 */

				if(!PropChunks(Handle,&VersionProps[0],1))
				{
					/* The following line tells iffparse to stop at the
					 * very beginning of a `Type' chunk contained in a
					 * `TERM' FORM chunk.
					 */

					if(!StopChunk(Handle,'TERM',Type))
					{
						/* Parse the file... */

						if(!ParseIFF(Handle,IFFPARSE_SCAN))
						{
							/* Did we get a version ID? */

							if(Prop = FindProp(Handle,'TERM','VERS'))
							{
								TermInfo = (struct TermInfo *)Prop -> sp_Data;

								/* Is it the file format we are able
								 * to read?
								 */

								if(TermInfo -> Version == TermVersion && TermInfo -> Revision <= TermRevision && TermInfo -> Revision >= 6)
								{
									/* The file read pointer is positioned
									 * just in front of the first data
									 * to be read, so let's don't disappoint
									 * iffparse and read it.
									 */

									if(ReadChunkBytes(Handle,Data,Size) == Size)
										Success = TRUE;
								}
							}
						}
					}
				}

				CloseIFF(Handle);
			}

			Close(Handle -> iff_Stream);
		}

		FreeIFF(Handle);
	}

	return(Success);
}

	/* PushWindow(struct Window *Window):
	 *
	 *	Push/PopWindow implement a single lifo window stack
	 *	which always updates the window to activate when
	 *	LSHIFT+RSHIFT+RETURN is pressed. This routine will
	 *	push a window on the stack.
	 */

VOID
PushWindow(struct Window *Window)
{
	if(WindowStackPtr < 5)
	{
		WindowStack[WindowStackPtr++] = Window;

		TopWindow = Window;
	}
}

	/* PopWindow():
	 *
	 *	Remove topmost window from window stack.
	 */

VOID
PopWindow()
{
	if(WindowStackPtr > 0)
	{
		WindowStackPtr--;

		if(WindowStackPtr)
			TopWindow = WindowStack[WindowStackPtr - 1];
		else
			TopWindow = Window;
	}
}
