/*
**	Tools.c
**
**	Miscellaneous support routines
**
**	Copyright © 1990-1996 by Olaf `Olsen' Barthel
**		All Rights Reserved
*/

#ifndef _GLOBAL_H
#include "Global.h"
#endif

	/* This variable stores the current state of the
	 * dialing menu items.
	 */

STATIC BOOLEAN DialItemsAvailable = TRUE;

	/* Current timer state. */

STATIC BOOLEAN TimerRunning;

	/* ListViewStateFill(struct LVDrawMsg *Msg):
	 *
	 *	Draw the ghosting pattern over a ghosted listview line.
	 */

VOID
ListViewStateFill(struct LVDrawMsg *Msg)
{
	if(Msg->lvdm_State == LVR_SELECTEDDISABLED || Msg->lvdm_State == LVR_NORMALDISABLED)
	{
		ULONG		 Ghosting	= 0x44441111;
		struct RastPort	*RPort		= Msg->lvdm_RastPort;

		SetABPenDrMd(RPort,Msg->lvdm_DrawInfo->dri_Pens[SHADOWPEN],0,JAM1);

		SetAfPt(RPort,(UWORD *)&Ghosting,1);

		RectFill(RPort,Msg->lvdm_Bounds.MinX,Msg->lvdm_Bounds.MinY,Msg->lvdm_Bounds.MaxX,Msg->lvdm_Bounds.MaxY);

		SetAfPt(RPort,NULL,0);
	}
}

	/* EraseWindow(struct Window *Window):
	 *
	 *	Fills the interior of a window in the background colour.
	 */

VOID
EraseWindow(struct Window *Window,UWORD *Pens)
{
	LONG Left,Top,Right,Bottom;

	Left	= Window->BorderLeft;
	Top	= Window->BorderTop;
	Right	= Window->Width - (Window->BorderRight + 1);
	Bottom	= Window->Height - (Window->BorderBottom + 1);

	if(Pens)
	{
		SetPens(RPort,Pens[BACKGROUNDPEN],0,JAM1);

		RectFill(Window->RPort,Left,Top,Right,Bottom);
	}
	else
		EraseRect(Window->RPort,Left,Top,Right,Bottom);
}

	/* GetListMaxPen(UWORD *Pens):
	 *
	 *	Calculates the maximum pen number index to be
	 *	used for listview text rendering.
	 */

LONG
GetListMaxPen(const UWORD *Pens)
{
	STATIC BYTE const PenTable[] =
	{
		TEXTPEN,
		BACKGROUNDPEN,
		FILLTEXTPEN,
		FILLPEN,
		SHADOWPEN,
		HIGHLIGHTTEXTPEN,
	};

	LONG Max,Pen,i;

	for(i = 0, Max = -1 ; i < NumElements(PenTable) ; i++)
	{
		Pen = Pens[PenTable[i]];

		if(Pen > Max)
			Max = Pen;
	}

	return(Max);
}

	/* FillBox(struct RastPort *RPort,LONG Left,LONG Top,LONG Width,LONG Height):
	 *
	 *	Fill a rectangular area with the current foregroung colour.
	 */

VOID
FillBox(struct RastPort *RPort,LONG Left,LONG Top,LONG Width,LONG Height)
{
	if(Width > 0 && Height > 0)
		RectFill(RPort,Left,Top,Left + Width - 1,Top + Height - 1);
}

	/* PlaceText(struct RastPort *RPort,LONG Left,LONG Top,STRPTR String,LONG Len):
	 *
	 *	Print some text at a specific position.
	 */

VOID
PlaceText(struct RastPort *RPort,LONG Left,LONG Top,STRPTR String,LONG Len)
{
	Move(RPort,Left,Top + RPort->TxBaseline);
	Text(RPort,String,Len);
}

	/* SetPens(struct RastPort *RPort,ULONG APen,ULONG BPen,ULONG DrMd):
	 *
	 *	Set rendering attributes.
	 */

VOID
SetPens(struct RastPort *RPort,ULONG APen,ULONG BPen,ULONG DrMd)
{
	if(Kick30)
		SetABPenDrMd(RPort,APen,BPen,DrMd);
	else
	{
		if(APen != RPort->FgPen)
			SetAPen(RPort,APen);

		if(BPen != RPort->BgPen)
			SetBPen(RPort,BPen);

		if(DrMd != RPort->DrawMode)
			SetDrMd(RPort,DrMd);
	}
}

	/* VSPrintf(STRPTR Buffer,STRPTR FormatString,va_list VarArgs):
	 *
	 *	Just like vsprintf(), but using the ROM routines.
	 */

VOID
VSPrintf(STRPTR Buffer,STRPTR FormatString,va_list VarArgs)
{
	RawDoFmt(FormatString,VarArgs,(VOID (*)())"\x16\xC0\x4E\x75",Buffer);
}

	/* SPrintf(STRPTR Buffer,STRPTR FormatString,...):
	 *
	 *	Just like sprintf(), but using the ROM routines.
	 */

VOID __stdargs
SPrintf(STRPTR Buffer,STRPTR FormatString,...)
{
	va_list VarArgs;

	va_start(VarArgs,FormatString);
	VSPrintf(Buffer,FormatString,VarArgs);
	va_end(VarArgs);
}

	/* Atol(STRPTR Buffer):
	 *
	 *	Convert ASCII representation to long word.
	 */

LONG
Atol(STRPTR Buffer)
{
	LONG Result;

	StrToLong(Buffer,&Result);

	return(Result);
}

	/* StripSpaces(STRPTR String):
	 *
	 *	Strip leading and trailing spaces from a string.
	 */

VOID
StripSpaces(STRPTR String)
{
	if(String)
	{
		STRPTR	To = String;
		LONG	Len;

		while(*String == ' ')
			String++;

		if(Len = strlen(String))
		{
			while(Len > 0 && String[Len - 1] == ' ')
				Len--;

			while(Len--)
				*To++ = *String++;
		}

		*To = 0;
	}
}

	/* ReplaceWindowInfo(struct WindowInfo *NewInfo):
	 *
	 *	Drop the window info into the right place.
	 */

VOID
ReplaceWindowInfo(struct WindowInfo *NewInfo)
{
	LONG i;

	for(i = 0 ; i < WINDOW_COUNT ; i++)
	{
		if(WindowInfoTable[i] . ID == NewInfo -> ID)
		{
			CopyMem(NewInfo,&WindowInfoTable[i],sizeof(struct WindowInfo));

			break;
		}
	}
}

	/* PutWindowInfo():
	 *
	 *	Store window size and position relative to
	 *	the main window.
	 */

VOID
PutWindowInfo(LONG ID,LONG Left,LONG Top,LONG Width,LONG Height)
{
	struct WindowInfo	*Info;
	LONG			 i;
	LONG			 WindowLeft,
				 WindowTop,
				 WindowWidth,
				 WindowHeight;
	ULONG			 IntuiLock;

	for(i = 0 ; i < WINDOW_COUNT ; i++)
	{
		if(WindowInfoTable[i] . ID == ID)
		{
			Info = &WindowInfoTable[i];

			break;
		}
	}

	IntuiLock = LockIBase(NULL);

	WindowLeft	= Window -> LeftEdge;
	WindowTop	= Window -> TopEdge;
	WindowWidth	= Window -> Width;
	WindowHeight	= Window -> Height;

	UnlockIBase(IntuiLock);

	Forbid();

	Info -> WindowFlags = NULL;

	if(WindowWidth == Width && WindowLeft == Left)
		Info -> WindowFlags |= WC_EXPANDWIDTH;
	else
	{
		if(Left == WindowLeft + WindowWidth)
			Info -> WindowFlags |= WC_ALIGNSIDE;

		if(WindowLeft == Left)
			Info -> WindowFlags |= WC_ALIGNLEFT;

		if(WindowLeft + WindowWidth == Left + Width)
			Info -> WindowFlags |= WC_ALIGNRIGHT;
	}

	if(WindowHeight == Height && WindowTop == Top)
		Info -> WindowFlags |= WC_EXPANDHEIGHT;
	else
	{
		if(Top == WindowTop + WindowHeight)
			Info -> WindowFlags |= WC_ALIGNBELOW;

		if(WindowTop == Top)
			Info -> WindowFlags |= WC_ALIGNTOP;

		if(WindowTop + WindowHeight == Top + Height)
			Info -> WindowFlags |= WC_ALIGNBOTTOM;
	}

	Info -> Left	= Left;
	Info -> Top	= Top;
	Info -> Width	= Width;
	Info -> Height	= Height;

	Permit();
}

	/* GetWindowInfo():
	 *
	 *	Set the window size and position in relation to
	 *	the main window.
	 */

VOID
GetWindowInfo(LONG ID,LONG *Left,LONG *Top,LONG *Width,LONG *Height,LONG DefWidth,LONG DefHeight)
{
	struct WindowInfo	*Info;
	LONG			 i;
	LONG			 WindowLeft,
				 WindowTop,
				 WindowWidth,
				 WindowHeight;
	ULONG			 IntuiLock;

	if(Window)
	{
		if(DefWidth && DefWidth < Window -> Width / 2)
			DefWidth = Window -> Width / 2;

		if(DefHeight && DefHeight < Window -> Height / 2)
			DefHeight = Window -> Height / 2;
	}

	for(i = 0 ; i < WINDOW_COUNT ; i++)
	{
		if(WindowInfoTable[i] . ID == ID)
		{
			Info = &WindowInfoTable[i];

			break;
		}
	}

	if(Info && ID == WINDOW_MAIN)
	{
		*Left	= Info -> Left;
		*Top	= Info -> Top;

		return;
	}

	if(Window)
	{
		IntuiLock = LockIBase(NULL);

		WindowLeft	= Window -> LeftEdge;
		WindowTop	= Window -> TopEdge + Window -> BorderTop;
		WindowWidth	= Window -> Width;
		WindowHeight	= Window -> Height - Window -> BorderTop;

		UnlockIBase(IntuiLock);
	}

	Forbid();

	if(*Width)
	{
		if(Info -> Width)
			*Left = Info -> Left;
		else
			*Left = WindowLeft + (WindowWidth - *Width) / 2;
	}
	else
	{
		if(DefWidth && !Info -> Width)
		{
			*Width	= DefWidth;
			*Left	= WindowLeft + (WindowWidth - *Width) / 2;
		}
		else
		{
			if(Info -> Width)
			{
				*Width	= Info -> Width;
				*Left	= Info -> Left;
			}
			else
			{
				*Width	= WindowWidth;
				*Left	= WindowLeft;
			}
		}
	}

	if(*Height)
	{
		if(Info -> Height)
			*Top = Info -> Top;
		else
			*Top = WindowTop + (WindowHeight - *Height) / 2;
	}
	else
	{
		if(DefHeight && !Info -> Height)
		{
			*Height	= DefHeight;
			*Top	= WindowTop + (WindowHeight - *Height) / 2;
		}
		else
		{
			if(Info -> Height)
			{
				*Height	= Info -> Height;
				*Top	= Info -> Top;
			}
			else
			{
				*Height	= WindowHeight;
				*Top	= WindowTop;
			}
		}
	}

	if(Info -> WindowFlags & WC_ALIGNSIDE)
		*Left = WindowLeft + WindowWidth;

	if(Info -> WindowFlags & WC_ALIGNBELOW)
		*Top = WindowTop + WindowHeight;

	if(Info -> WindowFlags & WC_ALIGNLEFT)
		*Left = WindowLeft;

	if(Info -> WindowFlags & WC_ALIGNTOP)
		*Top = WindowTop;

	if(Info -> WindowFlags & WC_ALIGNRIGHT)
		*Left = WindowLeft + WindowWidth - *Width;

	if(Info -> WindowFlags & WC_ALIGNBOTTOM)
		*Top = WindowTop + WindowHeight - *Height;

	if(Info -> WindowFlags & WC_EXPANDWIDTH)
		*Width = WindowWidth;

	if(Info -> WindowFlags & WC_EXPANDHEIGHT)
		*Height = WindowHeight;

	Permit();
}

	/* GetBitMapDepth(struct BitMap *BitMap):
	 *
	 *	Return the depth of a BitMap.
	 */

LONG
GetBitMapDepth(struct BitMap *BitMap)
{
	LONG Depth;

	if(Kick30)
		Depth = (LONG)GetBitMapAttr(BitMap,BMA_DEPTH);
	else
		Depth = BitMap -> Depth;

	if(Depth > 8)
		Depth = 8;

	return(Depth);
}

	/* GetDPI(ULONG Mode,ULONG *X_DPI,ULONG *Y_DPI):
	 *
	 *	Get screen DPI resolution values.
	 */

VOID
GetDPI(ULONG Mode,ULONG *X_DPI,ULONG *Y_DPI)
{
	struct DisplayInfo DisplayInfo;

	*X_DPI = *Y_DPI = 72;

	if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,Mode))
	{
		if(DisplayInfo . PixelSpeed && DisplayInfo . Resolution . x && DisplayInfo . Resolution . y)
		{
			*X_DPI = (35 * 140) / DisplayInfo . PixelSpeed;
			*Y_DPI = (*X_DPI * DisplayInfo . Resolution . x) / DisplayInfo . Resolution . y;
		}
	}
}

	/* AddProtection(STRPTR FileName,ULONG Mask):
	 *
	 *	Set bits in the protection mask of a file.
	 */

VOID
AddProtection(STRPTR Name,ULONG Mask)
{
	struct FileInfoBlock *FileInfo;

	if(FileInfo = (struct FileInfoBlock *)AllocDosObject(DOS_FIB,TAG_DONE))
	{
		BPTR FileLock;

		if(FileLock = Lock(Name,ACCESS_READ))
		{
			if(Examine(FileLock,FileInfo))
			{
				UnLock(FileLock);

				SetProtection(Name,FileInfo -> fib_Protection | Mask);
			}
			else
				UnLock(FileLock);
		}

		FreeDosObject(DOS_FIB,FileInfo);
	}
}

	/* GetPubScreenName(struct Screen *Screen,STRPTR Name):
	 *
	 *	Get the name of a public screen.
	 */

BOOL
GetPubScreenName(struct Screen *Screen,STRPTR Name)
{
	struct List		*PubScreenList;
	struct PubScreenNode	*ScreenNode;

	PubScreenList = LockPubScreenList();

	for(ScreenNode = (struct PubScreenNode *)PubScreenList -> lh_Head ; ScreenNode -> psn_Node . ln_Succ ; ScreenNode = (struct PubScreenNode *)ScreenNode -> psn_Node . ln_Succ)
	{
		if(ScreenNode -> psn_Screen == Screen)
		{
			strcpy(Name,ScreenNode -> psn_Node . ln_Name);

			UnlockPubScreenList();

			return(TRUE);
		}
	}

	UnlockPubScreenList();

	return(FALSE);
}

	/* InitSinglePort(struct MsgPort *Port):
	 *
	 *	Initialize a plain MsgPort (as created on the stack) for
	 *	usage. Don't try this at home, kids!
	 */

VOID
InitSinglePort(struct MsgPort *Port)
{
	memset(Port,0,sizeof(struct MsgPort));

	Port -> mp_Flags	= PA_SIGNAL;
	Port -> mp_SigBit	= SIGB_SINGLE;
	Port -> mp_SigTask	= FindTask(NULL);

	NewList(&Port -> mp_MsgList);
}

	/* GoodStream(BPTR Stream):
	 *
	 *	Check to see whether the current input file
	 *	is an interactive stream.
	 */

BOOL
GoodStream(BPTR Stream)
{
	if(!Stream)
		Stream = Input();

	if(Stream)
	{
		struct FileHandle *Handle = (struct FileHandle *)BADDR(Stream);

		if(Handle -> fh_Type)
		{
			if(IsInteractive(Stream))
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* GetProgramIcon():
	 *
	 *	Try to find the icon of the program.
	 */

struct DiskObject *
GetProgramIcon()
{
	struct DiskObject *Icon = NULL;

		/* Run from Workbench? */

	if(WBenchMsg)
	{
			/* Sanity check. */

		if(WBenchMsg -> sm_ArgList)
		{
				/* Yet another sanity check. */

			if(WBenchMsg -> sm_ArgList -> wa_Name)
			{
					/* Try to find the icon in the current directory. */

				if(Icon = GetDiskObjectNew(WBenchMsg -> sm_ArgList -> wa_Name))
				{
					if(Icon -> do_Type != WBTOOL)
					{
						FreeDiskObject(Icon);

						Icon = NULL;
					}
				}

				if(!Icon)
				{
					BPTR NewLock;

						/* Move to the directory the
						 * program was run from.
						 */

					if(NewLock = Lock("PROGDIR:",ACCESS_READ))
					{
						BPTR OldLock;

						OldLock = CurrentDir(NewLock);

							/* Try to fetch the icon, use the
							 * default name if necessary.
							 */

						if(Icon = GetDiskObjectNew(WBenchMsg -> sm_ArgList -> wa_Name))
						{
							if(Icon -> do_Type != WBTOOL)
							{
								FreeDiskObject(Icon);

								Icon = NULL;
							}
						}

						if(!Icon)
						{
							if(Icon = GetDiskObjectNew("term"))
							{
								if(Icon -> do_Type != WBTOOL)
								{
									FreeDiskObject(Icon);

									Icon = NULL;
								}
							}
						}

						CurrentDir(OldLock);

						UnLock(NewLock);
					}
				}
			}
		}
	}

		/* Still no success. */

	if(!Icon)
	{
			/* Use the default names. */

		if(Icon = GetDiskObjectNew("term"))
		{
			if(Icon -> do_Type != WBTOOL)
			{
				FreeDiskObject(Icon);

				Icon = NULL;
			}
		}

		if(!Icon)
		{
			if(Icon = GetDiskObjectNew("PROGDIR:term"))
			{
				if(Icon -> do_Type != WBTOOL)
				{
					FreeDiskObject(Icon);

					Icon = NULL;
				}
			}
		}
	}

	return(Icon);
}

	/* GetPenIndex(LONG Pen):
	 *
	 *	Get the table index corresponding to an on-screen
	 *	text rendering pen.
	 */

LONG
GetPenIndex(LONG Pen)
{
	LONG i;

	for(i = 0 ; i < 16 ; i++)
	{
		if(MappedPens[0][i] == Pen)
			return(i);
	}
}

	/* GetScreenWidth(struct Window *Window):
	 *
	 *	Query the current screen width.
	 */

LONG
GetScreenWidth(struct Window *Window)
{
	if(Window)
	{
		if(Window -> Width > ScreenWidth || Window -> LeftEdge < -Window -> WScreen -> LeftEdge || Window -> LeftEdge + Window -> WScreen -> LeftEdge + Window -> Width > ScreenWidth)
			return(ScreenWidth);
		else
			return(Window -> Width - (Window -> BorderLeft + Window -> BorderRight));
	}
	else
		return(ScreenWidth);
}

	/* GetScreenHeight(struct Window *Window):
	 *
	 *	Query the current screen height.
	 */

LONG
GetScreenHeight(struct Window *Window)
{
	if(Window)
	{
		if(Window -> Height > ScreenHeight || Window -> TopEdge < -Window -> WScreen -> TopEdge || Window -> TopEdge + Window -> WScreen -> TopEdge + Window -> Height > ScreenHeight)
			return(ScreenHeight);
		else
			return(Window -> Height - (Window -> BorderTop + Window -> BorderBottom));
	}
	else
		return(ScreenHeight);
}

	/* GetLeft(struct Screen *Screen):
	 *
	 *	Get the screen left edge.
	 */

STATIC LONG
GetLeft(struct Screen *Screen)
{
	if(Screen -> LeftEdge >= 0)
		return(0);
	else
		return(-Screen -> LeftEdge);
}

	/* GetScreenLeft(struct Window *Window):
	 *
	 *	Query the current screen left edge.
	 */

LONG
GetScreenLeft(struct Window *Window)
{
	if(Window)
	{
		if(Window -> Width > ScreenWidth || Window -> LeftEdge < -Window -> WScreen -> LeftEdge || Window -> LeftEdge + Window -> WScreen -> LeftEdge + Window -> Width > ScreenWidth)
			return(GetLeft(Window -> WScreen));
		else
			return(Window -> LeftEdge + Window -> BorderLeft);
	}
	else
	{
		struct Screen *PubScreen = LockPubScreen(NULL);
		LONG Result;

		Result = GetLeft(PubScreen ? PubScreen : SharedScreen);

		UnlockPubScreen(NULL,PubScreen);

		return(Result);
	}
}

	/* GetTop(struct Screen *Screen):
	 *
	 *	Get the screen top edge.
	 */

STATIC LONG
GetTop(struct Screen *Screen)
{
	if(Screen -> TopEdge >= 0)
		return(0);
	else
		return(-Screen -> TopEdge);
}

	/* GetScreenTop(struct Window *Window):
	 *
	 *	Query the current screen top edge.
	 */

LONG
GetScreenTop(struct Window *Window)
{
	if(Window)
	{
		if(Window -> Height > ScreenHeight || Window -> TopEdge < -Window -> WScreen -> TopEdge || Window -> TopEdge + Window -> WScreen -> TopEdge + Window -> Height > ScreenHeight)
			return(GetTop(Window -> WScreen));
		else
			return(Window -> TopEdge + Window -> BorderTop);
	}
	else
	{
		struct Screen *PubScreen = LockPubScreen(NULL);
		LONG Result;

		Result = GetTop(PubScreen ? PubScreen : SharedScreen);

		UnlockPubScreen(NULL,PubScreen);

		return(Result);
	}
}

	/* OldGetAPen(struct RastPort *RPort):
	 *
	 *	Query the current primary rendering colour (old style).
	 */

ULONG
OldGetAPen(struct RastPort *RPort)
{
	return((ULONG)RPort -> FgPen);
}

	/* OldGetBPen(struct RastPort *RPort):
	 *
	 *	Query the current seconary rendering colour (old style).
	 */

ULONG
OldGetBPen(struct RastPort *RPort)
{
	return((ULONG)RPort -> BgPen);
}

	/* OldGetDrMd(struct RastPort *RPort):
	 *
	 *	Query the current drawing mode (old style).
	 */

ULONG
OldGetDrMd(struct RastPort *RPort)
{
	return((ULONG)RPort -> DrawMode);
}

	/* OldSetWrMsk(struct RastPort *RPort,ULONG Mask):
	 *
	 *	Set the rendering plane mask (old style).
	 */

ULONG
OldSetWrMsk(struct RastPort *RPort,ULONG Mask)
{
	if(UseMasking)
		RPort -> Mask = Mask;

	return((ULONG)1);
}

	/* NewGetAPen(struct RastPort *RPort):
	 *
	 *	Query the current primary rendering colour (new style).
	 */

ULONG
NewGetAPen(struct RastPort *RPort)
{
	return(GetAPen(RPort));
}

	/* NewGetBPen(struct RastPort *RPort):
	 *
	 *	Query the current seconary rendering colour (new style).
	 */

ULONG
NewGetBPen(struct RastPort *RPort)
{
	return(GetBPen(RPort));
}

	/* NewGetDrMd(struct RastPort *RPort):
	 *
	 *	Query the current drawing mode (new style).
	 */

ULONG
NewGetDrMd(struct RastPort *RPort)
{
	return(GetDrMd(RPort));
}

	/* NewSetWrMsk(struct RastPort *RPort,ULONG Mask):
	 *
	 *	Set the rendering plane mask (new style).
	 */

ULONG
NewSetWrMsk(struct RastPort *RPort,ULONG Mask)
{
	if(UseMasking)
		return(SetWriteMask(RPort,Mask));
	else
		return((ULONG)1);
}

	/* SetWait(struct Window *Window):
	 *
	 *	Set the busy wait mouse pointer.
	 */

VOID
SetWait(struct Window *Window)
{
		// Note: now requires gtlayout.library to do all the hard work

	if(GTLayoutBase)
		LT_LockWindow(Window);
}

	/* ClrWait(struct Window *Window):
	 *
	 *	Remove the busy wait mouse pointer.
	 */

VOID
ClrWait(struct Window *Window)
{
	if(GTLayoutBase)
		LT_UnlockWindow(Window);
}

	/* GetModeName(ULONG Mode):
	 *
	 *	Get the name of a display mode.
	 */

STRPTR
GetModeName(ULONG Mode)
{
	STATIC UBYTE	Buffer[DISPLAYNAMELEN + 1];
	struct NameInfo	NameInfo;

	if(GetDisplayInfoData(NULL,(APTR)&NameInfo,sizeof(struct NameInfo),DTAG_NAME,Mode))
		strcpy(Buffer,NameInfo . Name);
	else
	{
		struct DimensionInfo DimensionInfo;

		if(GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(struct DimensionInfo),DTAG_DIMS,Mode))
		{
			STRPTR MonitorName;

			switch(Mode & MONITOR_ID_MASK)
			{
				case NTSC_MONITOR_ID:

					MonitorName = "NTSC:";
					break;

				case PAL_MONITOR_ID:

					MonitorName = "PAL:";
					break;

				case VGA_MONITOR_ID:

					MonitorName = "VGA:";
					break;

				case A2024_MONITOR_ID:

					MonitorName = "A2024:";
					break;

				default:

					MonitorName = "";
					break;
			}

			SPrintf(Buffer,"%s%ldx%ld",MonitorName,DimensionInfo . TxtOScan . MaxX - DimensionInfo . TxtOScan . MinX + 1,DimensionInfo . TxtOScan . MaxY - DimensionInfo . TxtOScan . MinY + 1);
		}
		else
			strcpy(Buffer,LocaleString(MSG_SCREENPANEL_UNKNOWN_TXT));
	}

	return(Buffer);
}

	/* ModeOkay(ULONG ID):
	 *
	 *	Checks whether a display mode ID will do for deep
	 *	screen bitmaps.
	 */

BOOL
ModeOkay(ULONG ID)
{
	struct DimensionInfo DimensionInfo;

	if(GetDisplayInfoData(NULL,(APTR)&DimensionInfo,sizeof(struct DimensionInfo),DTAG_DIMS,ID))
	{
		if(DimensionInfo . MaxDepth >= 4)
			return(TRUE);
	}

	return(FALSE);
}

	/* SetClipMenu(BYTE Mode):
	 *
	 *	Enable/disable the copy/clear selection menu items.
	 */

VOID
SetClipMenu(BOOL Mode)
{
	if(Mode && RasterEnabled)
	{
		OnItem(MEN_COPY);
		OnItem(MEN_CLEAR);
	}
	else
	{
		OffItem(MEN_COPY);
		OffItem(MEN_CLEAR);
	}
}

	/* SetRedialMenu():
	 *
	 *	Make the `redial' menu item available or make it
	 *	unavailable.
	 */

VOID
SetRedialMenu()
{
	BOOL Mode;

	if(DialList)
	{
		if(DialList -> lh_Head -> ln_Succ)
			Mode = TRUE;
		else
			Mode = FALSE;
	}
	else
		Mode = FALSE;

	if(Mode && (DialItemsAvailable || Config -> MiscConfig -> ProtectiveMode))
		OnItem(MEN_REDIAL);
	else
		OffItem(MEN_REDIAL);
}

	/* SetDialMenu(BOOL Mode):
	 *
	 *	Block or enable the dialing menu.
	 */

VOID
SetDialMenu(BOOL Mode)
{
	if(Window && Menu)
	{
		if(Mode || Config -> MiscConfig -> ProtectiveMode)
		{
			if(DialList)
			{
				if(DialList -> lh_Head -> ln_Succ)
					OnItem(MEN_REDIAL);
				else
					OffItem(MEN_REDIAL);
			}
			else
				OffItem(MEN_REDIAL);

			OnItem(MEN_DIAL_NUMBER);

			if(FirstDialMenu != -1)
				OnItem(MEN_EXTRA_DIAL);
		}
		else
		{
			OffItem(MEN_REDIAL);
			OffItem(MEN_DIAL_NUMBER);

			if(FirstDialMenu != -1)
				OffItem(MEN_EXTRA_DIAL);
		}

		DialItemsAvailable = Mode;
	}
}

	/* SetTransferMenu(BOOL Mode):
	 *
	 *	Block or enable the transfer menu.
	 */

VOID
SetTransferMenu(BOOL Mode)
{
	if(Window && Menu)
	{
		BOOL	ValidDefault,
			ValidASCIIDownload,
			ValidASCIIUpload,
			ValidTextDownload,
			ValidTextUpload,
			ValidBinaryDownload,
			ValidBinaryUpload;

		if(!Config -> TransferConfig -> DefaultLibrary[0] || !Mode || (!XProtocolBase && Config -> TransferConfig -> DefaultType == XFER_XPR))
			ValidDefault = FALSE;
		else
			ValidDefault = TRUE;

		switch(Config -> TransferConfig -> ASCIIUploadType)
		{
			case XFER_INTERNAL:

				ValidASCIIUpload = TRUE;
				break;

			case XFER_DEFAULT:

				ValidASCIIUpload = ValidDefault;
				break;

			case XFER_XPR:
			case XFER_EXTERNALPROGRAM:

				ValidASCIIUpload = Config -> TransferConfig -> ASCIIUploadLibrary[0];
				break;
		}

		switch(Config -> TransferConfig -> ASCIIDownloadType)
		{
			case XFER_INTERNAL:

				ValidASCIIDownload = TRUE;
				break;

			case XFER_DEFAULT:

				ValidASCIIDownload = ValidDefault;
				break;

			case XFER_XPR:
			case XFER_EXTERNALPROGRAM:

				ValidASCIIDownload = Config -> TransferConfig -> ASCIIDownloadLibrary[0];
				break;
		}


		switch(Config -> TransferConfig -> TextUploadType)
		{
			case XFER_DEFAULT:

				ValidTextUpload = ValidDefault;
				break;

			case XFER_XPR:
			case XFER_EXTERNALPROGRAM:

				ValidTextUpload = Config -> TransferConfig -> TextUploadLibrary[0];
				break;
		}

		switch(Config -> TransferConfig -> TextDownloadType)
		{
			case XFER_DEFAULT:

				ValidTextDownload = ValidDefault;
				break;

			case XFER_XPR:
			case XFER_EXTERNALPROGRAM:

				ValidTextDownload = Config -> TransferConfig -> TextDownloadLibrary[0];
				break;
		}

		switch(Config -> TransferConfig -> BinaryUploadType)
		{
			case XFER_DEFAULT:

				ValidBinaryUpload = ValidDefault;
				break;

			case XFER_XPR:
			case XFER_EXTERNALPROGRAM:

				ValidBinaryUpload = Config -> TransferConfig -> BinaryUploadLibrary[0];
				break;
		}

		switch(Config -> TransferConfig -> BinaryDownloadType)
		{
			case XFER_DEFAULT:

				ValidBinaryDownload = ValidDefault;
				break;

			case XFER_XPR:
			case XFER_EXTERNALPROGRAM:

				ValidBinaryDownload = Config -> TransferConfig -> BinaryDownloadLibrary[0];
				break;
		}

		if(ValidASCIIUpload)
			OnItem(MEN_UPLOAD_ASCII);
		else
			OffItem(MEN_UPLOAD_ASCII);

		if(ValidASCIIDownload)
			OnItem(MEN_DOWNLOAD_ASCII);
		else
			OffItem(MEN_DOWNLOAD_ASCII);

		if(ValidTextUpload)
		{
			OnItem(MEN_UPLOAD_TEXT);
			OnItem(MEN_EDIT_AND_UPLOAD_TEXT);
		}
		else
		{
			OffItem(MEN_UPLOAD_TEXT);
			OffItem(MEN_EDIT_AND_UPLOAD_TEXT);
		}

		if(ValidTextDownload)
			OnItem(MEN_DOWNLOAD_TEXT);
		else
			OffItem(MEN_DOWNLOAD_TEXT);

		if(ValidBinaryUpload)
			OnItem(MEN_UPLOAD_BINARY);
		else
			OffItem(MEN_UPLOAD_BINARY);

		if(ValidBinaryDownload)
			OnItem(MEN_DOWNLOAD_BINARY);
		else
			OffItem(MEN_DOWNLOAD_BINARY);

		if(ValidDefault)
			OnItem(MEN_TRANSFER);
		else
			OffItem(MEN_TRANSFER);
	}
}

	/* SetRasterMenu(BOOL Mode):
	 *
	 *	Block or enable the menu entries associated with
	 *	functions to access the screen raster.
	 */

VOID
SetRasterMenu(BOOL Mode)
{
	if(Window && Menu)
	{
		if(Mode)
		{
			OnItem(MEN_SAVE_AS_TEXT);
			OnItem(MEN_PRINT_SCREEN);
		}
		else
		{
			OffItem(MEN_SAVE_AS_TEXT);
			OffItem(MEN_PRINT_SCREEN);
		}
	}
}

	/* PickFont(struct Window *Window,STRPTR Name,WORD *Points,BOOL MonoSpaced):
	 *
	 *	Pick a font using asl.library
	 */

BOOL
PickFont(struct Window *Window,STRPTR Name,WORD *Points,BOOL MonoSpaced)
{
	struct TagItem			 DimensionTags[5];
	struct FontRequester		*Requester;
	BOOL				 Result = FALSE;

	if(Requester = (struct FontRequester *)AllocAslRequestTags(ASL_FontRequest,
		ASLFO_Window,		Window,
		ASLFO_InitialName,	Name,
		ASLFO_InitialSize,	*Points,
		ASLFO_InitialFrontPen,	Pens[TEXTPEN],
		ASLFO_InitialBackPen,	Pens[BACKGROUNDPEN],
		ASLFO_PrivateIDCMP,	TRUE,
		ASLFO_MaxHeight,	255,		// Excessive!

		ASL_FuncFlags,		MonoSpaced ? (FONF_NEWIDCMP|FONF_FIXEDWIDTH) : FONF_NEWIDCMP,
	TAG_MORE,GetDimensionTags(NULL,DimensionTags)))
	{
		LT_LockWindow(Window);

		if(AslRequest(Requester,NULL))
		{
			PutDimensionTags(NULL,Requester -> fo_LeftEdge,Requester -> fo_TopEdge,Requester -> fo_Width,Requester -> fo_Height);

			strcpy(Name,Requester -> fo_Attr . ta_Name);

			*Points = Requester -> fo_Attr . ta_YSize;

			Result = TRUE;
		}

		LT_UnlockWindow(Window);

		FreeAslRequest(Requester);
	}

	return(Result);
}

	/* ExtractString():
	 *
	 *	Extracts a string from a list separated by `|' characters.
	 */

STRPTR
ExtractString(STRPTR String,STRPTR Destination,BOOL ReturnEnd)
{
	STRPTR OldString;

	if(ReturnEnd)
		OldString = NULL;
	else
		OldString = String;

	while(*String)
	{
		if(*String == '|')
		{
			*Destination = 0;

			String++;

			if(*String)
				return(String);
			else
				return(OldString);
		}
		else
			*Destination++ = *String++;
	}

	*Destination = 0;

	return(OldString);
}

	/* GetListSize(struct List *List):
	 *
	 *	Determine the number of entries in a list.
	 */

LONG
GetListSize(struct List *List)
{
	struct Node	*Node	= List -> lh_Head;
	LONG		 i	= 0;

	while(Node -> ln_Succ)
	{
		i++;

		Node = Node -> ln_Succ;
	}

	return(i);
}

	/* GetListNode(LONG Offset,struct List *List):
	 *
	 *	Return the n-th Node entry in a standard exec list.
	 */

struct Node *
GetListNode(LONG Offset,struct List *List)
{
	if(Offset < 0)
		return(NULL);
	else
	{
		struct Node	*Node;
		LONG		 i;

		Node = List -> lh_Head;

		for(i = 0 ; i < Offset ; i++)
		{
			if(!Node -> ln_Succ -> ln_Succ)
				return(NULL);

			Node = Node -> ln_Succ;
		}

		return(Node);
	}
}

	/* CreateNode(STRPTR Name):
	 *
	 *	Put a name string into a list node.
	 */

struct Node *
CreateNode(STRPTR Name)
{
	struct Node *Node;

	if(Node = (struct Node *)AllocVecPooled(sizeof(struct Node) + strlen(Name) + 1,MEMF_ANY | MEMF_PUBLIC))
	{
		Node -> ln_Name = (STRPTR)(Node + 1);

		strcpy(Node -> ln_Name,Name);
	}

	return(Node);
}

	/* FreeNode(struct Node *Node):
	 *
	 *	Remove and deallocate a node.
	 */

VOID
FreeNode(struct Node *Node)
{
	Remove(Node);

	FreeVecPooled(Node);
}

	/* FreeList(struct List *List):
	 *
	 *	Remove all nodes from the list
	 *	and free them on the way.
	 */

VOID
FreeList(struct List *List)
{
	struct Node *Node;

	while(Node = RemHead(List))
		FreeVecPooled(Node);
}

	/* GetNodeOffset(struct Node *Node,struct List *List):
	 *
	 *	Scan a list for a certain node and return
	 *	its position.
	 */

LONG
GetNodeOffset(struct Node *Node,struct List *List)
{
	struct Node	*ListNode;
	LONG		 Offset = 0;

	ListNode = List -> lh_Head;

	while(ListNode -> ln_Succ)
	{
		if(Node == ListNode)
			return(Offset);
		else
		{
			Offset++;

			ListNode = ListNode -> ln_Succ;
		}
	}

	return(~0);
}

	/* MoveList(struct List *From,struct List *To):
	 *
	 *	Move the contents of a list to a different list.
	 */

VOID
MoveList(struct List *From,struct List *To)
{
	struct Node *Node;

	while(Node = RemHead(From))
		AddTail(To,Node);
}

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

VOID __stdargs
LogAction(STRPTR String,...)
{
	if(Config -> CaptureConfig -> LogActions && Config -> CaptureConfig -> LogFileName[0])
	{
		UBYTE	DummyBuffer[512];
		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 -> CaptureConfig -> LogFileName))
		{
				/* It does, let's append the data. */

			if(File = Open(Config -> CaptureConfig -> LogFileName,MODE_READWRITE))
			{
				if(Seek(File,0,OFFSET_END) == -1)
				{
					Close(File);

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

			if(File = Open(Config -> CaptureConfig -> LogFileName,MODE_NEWFILE))
				FPrintf(File,LocaleString(MSG_TERMAUX_DATE_TIME_ACTION_TXT));
		}

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

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

			DateStamp(&DateTime . dat_Stamp);

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

			if(DateToStr(&DateTime))
			{
				StripSpaces(TimeBuffer);

				FPrintf(File,"%-9s %8s %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);
}

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

BOOL
GetString(BOOL LoadGadget,BOOL Password,LONG MaxChars,STRPTR Prompt,STRPTR Buffer)
{
	enum	{	GAD_OK=1,GAD_CANCEL,GAD_STRING };

	struct LayoutHandle	*Handle;
	BOOL			 Success = FALSE;
	UBYTE			 LocalBuffer[256];

	if(!MaxChars)
		MaxChars = 255;

	if(MaxChars > 255)
	{
		CopyMem(Buffer,LocalBuffer,255);

		LocalBuffer[255] = 0;

		MaxChars = 255;
	}
	else
		strcpy(LocalBuffer,Buffer);

	if(!Prompt)
		Prompt = LocaleString(MSG_TERMXPR_INPUT_REQUIRED_TXT);

	if(Handle = LT_CreateHandleTags(Window -> WScreen,
		LH_LocaleHook,	&LocaleHook,
	TAG_DONE))
	{
		struct Window *PanelWindow;

		LT_New(Handle,
			LA_Type,	VERTICAL_KIND,
		TAG_DONE);
		{
			LT_New(Handle,
				LA_Type,	VERTICAL_KIND,
				LA_LabelText,	Prompt,
			TAG_DONE);
			{
				if(Password)
				{
					LT_New(Handle,
						LA_Type,	PASSWORD_KIND,
						LA_STRPTR,	LocalBuffer,
						LA_ID,		GAD_STRING,
						LA_Chars,	40,
						GTST_MaxChars,	MaxChars,
					TAG_DONE);
				}
				else
				{
					LT_New(Handle,
						LA_Type,	STRING_KIND,
						LA_STRPTR,	LocalBuffer,
						LA_ID,		GAD_STRING,
						LA_Chars,	60,
						LAST_Picker,	LoadGadget,
					TAG_DONE);
				}

				LT_EndGroup(Handle);
			}

			LT_New(Handle,
				LA_Type,VERTICAL_KIND,
			TAG_DONE);
			{
				LT_New(Handle,
					LA_Type,	XBAR_KIND,
					LAXB_FullSize,	TRUE,
				TAG_DONE);

				LT_EndGroup(Handle);
			}

			LT_New(Handle,LA_Type,HORIZONTAL_KIND,
				LAGR_SameSize,	TRUE,
				LAGR_Spread,	TRUE,
			TAG_DONE);
			{
				LT_New(Handle,
					LA_Type,	BUTTON_KIND,
					LA_LabelID,	MSG_TERMXPR_OKAY_GAD,
					LA_ID,		GAD_OK,
					LABT_ReturnKey,	TRUE,
					LABT_ExtraFat,	TRUE,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,	BUTTON_KIND,
					LA_LabelID,	MSG_GLOBAL_CANCEL_GAD,
					LA_ID,		GAD_CANCEL,
					LABT_EscKey,	TRUE,
					LABT_ExtraFat,	TRUE,
				TAG_DONE);

				LT_EndGroup(Handle);
			}
		}

		if(PanelWindow = LT_Build(Handle,
			LAWN_TitleID,		MSG_GLOBAL_ENTER_TEXT_TXT,
			LAWN_IDCMP,		IDCMP_CLOSEWINDOW,
			LAWN_HelpHook,		&GuideHook,
			LAWN_Parent,		Window,
			WA_DepthGadget,		TRUE,
			WA_CloseGadget,		TRUE,
			WA_DragBar,		TRUE,
			WA_RMBTrap,		TRUE,
			WA_Activate,		TRUE,
			WA_SimpleRefresh,	TRUE,
		TAG_DONE))
		{
			struct IntuiMessage	*Message;
			BOOL			 Done = FALSE;
			ULONG			 MsgClass;
			UWORD			 MsgCode;
			struct Gadget		*MsgGadget;

			LT_Activate(Handle,GAD_STRING);

			PushWindow(PanelWindow);

			LT_ShowWindow(Handle,TRUE);

			do
			{
				if(Wait(PORTMASK(PanelWindow -> UserPort) | SIG_BREAK) & SIG_BREAK)
					break;

				while(Message = (struct IntuiMessage *)LT_GetIMsg(Handle))
				{
					MsgClass	= Message -> Class;
					MsgCode		= Message -> Code;
					MsgGadget	= (struct Gadget *)Message -> IAddress;

					LT_ReplyIMsg(Message);

					if(MsgClass == IDCMP_CLOSEWINDOW)
						Done = TRUE;

					if(MsgClass == IDCMP_GADGETUP)
					{
						switch(MsgGadget -> GadgetID)
						{
							case GAD_STRING:

								if(MsgCode == '\r')
								{
									LT_UpdateStrings(Handle);

									strcpy(Buffer,LocalBuffer);

									Success = Done = TRUE;

									LT_PressButton(Handle,GAD_OK);
								}

								break;

							case GAD_OK:

								LT_UpdateStrings(Handle);

								strcpy(Buffer,LocalBuffer);

								Success = Done = TRUE;
								break;

							case GAD_CANCEL:

								Done = TRUE;
								break;
						}
					}

					if(MsgClass == IDCMP_IDCMPUPDATE && MsgGadget -> GadgetID == GAD_STRING)
					{
						UBYTE			 DummyBuffer[MAX_FILENAME_LENGTH],
									*DummyChar;
						struct FileRequester	*FileRequest;

						SplitFileName(LocalBuffer,&DummyChar,DummyBuffer);

						if(FileRequest = GetFile(PanelWindow,LocaleString(MSG_TERMAUX_LOAD_FILE_TXT),DummyBuffer,DummyChar,DummyBuffer,NULL,FALSE,FALSE,FALSE,LocaleString(MSG_GLOBAL_SELECT_TXT),TRUE))
						{
							LT_SetAttributes(Handle,GAD_STRING,GTST_String,DummyBuffer,TAG_DONE);

							FreeAslRequest(FileRequest);
						}
					}
				}
			}
			while(!Done);

			PopWindow();
		}

		LT_DeleteHandle(Handle);
	}

	return(Success);
}

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

VOID
WakeUp(struct Window *Window,LONG Sound)
{
	if(Window)
	{
		if(Config -> MiscConfig -> AlertMode == ALERT_SCREEN || Config -> MiscConfig -> AlertMode == ALERT_BEEP_SCREEN)
		{
			if(Window -> WScreen -> LeftEdge > 0)
			{
				if(Window -> WScreen -> TopEdge > 0)
					MoveScreen(Window -> WScreen,-Window -> WScreen -> LeftEdge,-Window -> WScreen -> TopEdge);
				else
					MoveScreen(Window -> WScreen,-Window -> WScreen -> LeftEdge,0);
			}
			else
			{
				if(Window -> WScreen -> TopEdge > 0)
					MoveScreen(Window -> WScreen,0,-Window -> WScreen -> TopEdge);
			}

			WindowToFront(Window);

			ScreenToFront(Window -> WScreen);
		}
	}

	if(Sound != SOUND_BELL || Config -> MiscConfig -> AlertMode == ALERT_BEEP || Config -> MiscConfig -> AlertMode == ALERT_BEEP_SCREEN)
		SoundPlay(Sound);
}

	/* TaskDestructor(struct DataMsg *Item):
	 *
	 *	Msg destructor for the routines below.
	 */

STATIC VOID __stdargs
TaskDestructor(struct DataMsg *Item)
{
	Signal((struct Process *)Item -> Data,SIGBREAKF_CTRL_F);
}

	/* AmigaDOSBackgroundServer(VOID):
	 *
	 *	Background process to handle tool execution.
	 */

STATIC VOID __saveds
AmigaDOSBackgroundServer(VOID)
{
	STATIC ULONG DefaultTags[] =
	{
		SYS_UserShell,TRUE,
		TAG_DONE
	};

	BPTR		 NewCOS = NULL;
	struct DataMsg	 Msg;
	STRPTR		 Command;
	struct Process	*Me;

		/* Look who we are. */

	Me = (struct Process *)FindTask(NULL);

	Command = Me -> pr_Task . tc_UserData;

		/* Create console output stream, will be closed automagically on exit. */

	if(!Output() && GetConsoleTask())
	{
		if(NewCOS = Open("CONSOLE:",MODE_NEWFILE))
			SelectOutput(NewCOS);
	}

	SystemTagList(Command,(struct TagItem *)DefaultTags);

	FreeVecPooled(Command);

	Forbid();

	InitMsgItem(&Msg,TaskDestructor);

	Msg . Type = DATAMSGTYPE_COMMANDDONE;
	Msg . Data = (UBYTE *)Me;

	ClrSignal(SIGBREAKF_CTRL_F);

	PutMsgItem(SpecialQueue,(struct MsgItem *)&Msg);

	Wait(SIGBREAKF_CTRL_F);

	if(NewCOS)
	{
		SelectOutput(NULL);
		Close(NewCOS);
	}
}

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

VOID
SendAmigaDOSCommand(STRPTR Name)
{
	STRPTR NewName;

	if(NewName = (STRPTR)AllocVecPooled(strlen(Name) + 1 + 256,MEMF_ANY))
	{
		struct Process	*NewProcess;
		BPTR		 Stream;
		STRPTR		 NewWindowName;

		strcpy(NewName,Name);

		NewWindowName = NewName + strlen(NewName) + 1;

		BlockWindows();

		SetQueueDiscard(SpecialQueue,FALSE);

			/* Open the output file. */

		if(WindowName[0])
		{
			UBYTE LocalName[MAXPUBSCREENNAME + 1];

			if(Window)
			{
				if(!GetPubScreenName(Window -> WScreen,LocalName))
					LocalName[0] = 0;
			}

			if(LocalName[0])
			{
				SPrintf(NewWindowName,WindowName,LocalName);

				Stream = Open(NewWindowName,MODE_NEWFILE);
			}
			else
				Stream = Open(WindowName,MODE_NEWFILE);
		}
		else
			Stream = NULL;

		Forbid();

		if(NewProcess = LaunchProcess("term AmigaDOS Background Process",AmigaDOSBackgroundServer,Stream))
		{
			CantQuit++;

			NewProcess -> pr_Task . tc_UserData = NewName;

			Permit();
		}
		else
		{
			Permit();

			if(Stream)
				Close(Stream);

			FreeVecPooled(NewName);

			BumpWindow(Window);

			ReleaseWindows();
		}
	}
}

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

STATIC VOID __saveds
RexxBackgroundServer(VOID)
{
	struct MsgPort 	*RexxPort;
	BPTR		 NewCOS = NULL;
	struct DataMsg	 Msg;
	STRPTR		 Command;
	struct Process	*Me;

		/* Look who we are. */

	Me = (struct Process *)FindTask(NULL);

	Command = Me -> pr_Task . tc_UserData;

		/* Create console output stream, will be closed automagically on exit. */

	if(!Output() && GetConsoleTask())
	{
		if(NewCOS = Open("CONSOLE:",MODE_NEWFILE))
			SelectOutput(NewCOS);
	}

	if(RexxPort = FindPort(RXSDIR))
	{
		struct MsgPort __aligned	 ReplyPort;
		struct RexxMsg			*RexxMsg;

		InitSinglePort(&ReplyPort);

		if(RexxMsg = CreateRexxMsg(&ReplyPort,"term",RexxPortName))
		{
			if(RexxMsg -> rm_Args[0] = CreateArgstring(Command,strlen(Command)))
			{
				RexxMsg -> rm_Action = RXCOMM;

				Forbid();

				ClrSignal(SIGF_SINGLE);

				PutMsg(RexxPort,(struct Message *)RexxMsg);

				WaitPort(&ReplyPort);

				GetMsg(&ReplyPort);

				Permit();

					/* This doesn't look too
					 * good, does it?
					 */

				if(RexxMsg -> rm_Result1 && Output())
					Printf(LocaleString(MSG_TERMAUX_COMMAND_HAS_TERMINATED_TXT),Command,RexxMsg -> rm_Result1,RexxMsg -> rm_Result2);

				DeleteArgstring(RexxMsg -> rm_Args[0]);
			}

			DeleteRexxMsg(RexxMsg);
		}
	}

	FreeVecPooled(Command);

	Forbid();

	InitMsgItem(&Msg,TaskDestructor);

	Msg . Type = DATAMSGTYPE_COMMANDDONE;
	Msg . Data = (UBYTE *)Me;

	ClrSignal(SIGBREAKF_CTRL_F);

	PutMsgItem(SpecialQueue,(struct MsgItem *)&Msg);

	Wait(SIGBREAKF_CTRL_F);

	if(NewCOS)
	{
		SelectOutput(NULL);

		Close(NewCOS);
	}
}

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

VOID
SendARexxCommand(STRPTR Name,BOOL QueueIt)
{
	STRPTR NewName;

	if(QueueIt)
	{
		ObtainSemaphore(&ARexxQueueSemaphore);

		if(ARexxRunning)
		{
			struct Node *Node;

			if(Node = CreateNode(Name))
				AddTail(&ARexxQueue,Node);

			ReleaseSemaphore(&ARexxQueueSemaphore);

			return;
		}
	}

	if(NewName = (STRPTR)AllocVecPooled(strlen(Name) + 1 + 256,MEMF_ANY))
	{
		struct Process	*NewProcess;
		BPTR		 Stream;
		STRPTR		 NewWindowName;

		strcpy(NewName,Name);

		NewWindowName = NewName + strlen(NewName) + 1;

		BlockWindows();

		SetQueueDiscard(SpecialQueue,FALSE);

			/* Open the output file. */

		if(WindowName[0])
		{
			UBYTE LocalName[MAXPUBSCREENNAME + 1];

			if(Window)
			{
				if(!GetPubScreenName(Window -> WScreen,LocalName))
					LocalName[0] = 0;
			}

			if(LocalName[0])
			{
				SPrintf(NewWindowName,WindowName,LocalName);

				Stream = Open(NewWindowName,MODE_NEWFILE);
			}
			else
				Stream = Open(WindowName,MODE_NEWFILE);
		}
		else
			Stream = NULL;

		Forbid();

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

		if(NewProcess = LaunchProcess("term ARexx Background Process",RexxBackgroundServer,Stream))
		{
			CantQuit++;

			NewProcess -> pr_Task . tc_UserData = NewName;

			Permit();

			if(QueueIt)
				ARexxRunning = TRUE;
		}
		else
		{
			Permit();

			if(Stream)
				Close(Stream);

			FreeVecPooled(NewName);

			BumpWindow(Window);

			ReleaseWindows();
		}
	}

	if(QueueIt)
		ReleaseSemaphore(&ARexxQueueSemaphore);
}

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

VOID
BlockWindows()
{
	if(!(BlockNestCount++))
	{
		LT_LockWindow(Window);
		LT_LockWindow(StatusWindow);
		LT_LockWindow(FastWindow);
		LT_LockWindow(MatrixWindow);

		WeAreBlocking = TRUE;

		SetQueueDiscard(SpecialQueue,TRUE);

		GhostCursor();
	}
}

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

VOID
ReleaseWindows()
{
	if(BlockNestCount == 1)
	{
		LT_UnlockWindow(Window);
		LT_UnlockWindow(StatusWindow);
		LT_UnlockWindow(FastWindow);
		LT_UnlockWindow(MatrixWindow);

		WeAreBlocking = FALSE;

		SetQueueDiscard(SpecialQueue,FALSE);

		if(Window)
		{
			Forbid();

			if(Window -> Flags & WFLG_WINDOWACTIVE)
				NormalCursor();

			Permit();
		}
	}

	if(BlockNestCount)
		BlockNestCount--;
}

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

LONG
LineRead(BPTR File,STRPTR Buffer,LONG MaxChars)
{
	STATIC UBYTE	Data[1024];
	STATIC LONG	ReadIndex,ReadLen;

	if(File)
	{
		LONG BytesRead = 0,i;

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

				ReadIndex = 0;

				if(ReadLen <= 0)
				{
					Buffer[i] = 0;

					return(BytesRead);
				}
			}

			BytesRead++;

			if((Buffer[i] = Data[ReadIndex++]) == '\n')
			{
				Buffer[i + 1] = 0;

				return(BytesRead);
			}
		}

		return(BytesRead);
	}
	else
		ReadIndex = ReadLen = 0;
}

	/* GetBaudRate(STRPTR Buffer):
	 *
	 *	Calculate the baud rate contained in a connect string.
	 */

LONG
GetBaudRate(STRPTR Buffer)
{
	LONG Rate,i,j;

	for(i = j = 0 ; i < strlen(Buffer) ; i++)
	{
		if(Buffer[i] == ' ')
			continue;
		else
		{
			if(Buffer[i] >= '0' && Buffer[i] <= '9')
				SharedBuffer[j++] = Buffer[i];
			else
				break;
		}
	}

	SharedBuffer[j] = 0;

	if(StrToLong(SharedBuffer,&Rate) > 0)
	{
		if(Rate > 0)
			return(Rate);
	}

	return(0);
}

	/* LookForIt(struct MenuItem *Item,ULONG ID):
	 *
	 *	Auxilary routine for FindThisItem(), scans
	 *	menu item and sub item lists.
	 */

STATIC struct MenuItem *
LookForIt(struct MenuItem *Item,ULONG ID)
{
	while(Item)
	{
		if((ULONG)GTMENUITEM_USERDATA(Item) == ID)
			return(Item);
		else
		{
			if(Item -> SubItem)
			{
				register struct MenuItem *TheItem;

				if(TheItem = LookForIt(Item -> SubItem,ID))
					return(TheItem);
			}

			Item = Item -> NextItem;
		}
	}

	return(NULL);
}

	/* FindThisItem(struct Menu *FirstMenu,ULONG MenuID):
	 *
	 *	Scan a menu for a menuitem associated with a
	 *	menu ID.
	 */

struct MenuItem *
FindThisItem(struct Menu *FirstMenu,ULONG MenuID)
{
	if(TypeOfMem(FirstMenu))
	{
		struct MenuItem *Item;

		while(FirstMenu)
		{
			if(Item = LookForIt(FirstMenu -> FirstItem,MenuID))
				return(Item);
			else
				FirstMenu = FirstMenu -> NextMenu;
		}
	}

	return(NULL);
}

struct Menu *
FindThisMenu(struct Menu *FirstMenu,ULONG MenuID)
{
	if(TypeOfMem(FirstMenu))
	{
		while(FirstMenu)
		{
			if(GTMENU_USERDATA(FirstMenu) == (APTR)MenuID)
				return(FirstMenu);
			else
				FirstMenu = FirstMenu -> NextMenu;
		}
	}

	return(NULL);
}

	/* GetItem(ULONG MenuID):
	 *
	 *	Get the checkmark state of a menu item.
	 */

BOOL
GetItem(ULONG MenuID)
{
	if(Menu)
	{
		struct MenuItem *Item;

		if(Item = FindThisItem(Menu,MenuID))
		{
			if(Item -> Flags & CHECKED)
				return(TRUE);
		}
	}

	return(FALSE);
}

	/* SetItem(ULONG MenuID,BOOL Mode):
	 *
	 *	Clear or set the checkmark or state of a menu item.
	 */

VOID
SetItem(ULONG MenuID,BOOL Mode)
{
		/* Is the global pull-down menu available? */

	if(Menu)
	{
		struct MenuItem	*Item;
		struct Menu	*ThisMenu;

		if(ThisMenu = FindThisMenu(Menu,MenuID))
		{
				/* Remove the menu from the windows. */

			if(Window)
				ClearMenuStrip(Window);

			if(StatusWindow)
				ClearMenuStrip(StatusWindow);

			if(FastWindow)
				ClearMenuStrip(FastWindow);

			switch(Mode)
			{
				case SETITEM_ON:

					ThisMenu -> Flags |= MENUENABLED;
					break;

				case SETITEM_OFF:

					ThisMenu -> Flags &= ~MENUENABLED;
					break;
			}

				/* Reattach the menu to the windows. */

			if(Window)
				ResetMenuStrip(Window,Menu);

			if(StatusWindow)
				ResetMenuStrip(StatusWindow,Menu);

			if(FastWindow)
				ResetMenuStrip(FastWindow,Menu);

			return;
		}

			/* Try to find the menu item and change
			 * the state of the checkmark.
			 */

		if(Item = FindThisItem(Menu,MenuID))
		{
				/* Remove the menu from the windows. */

			if(Window)
				ClearMenuStrip(Window);

			if(StatusWindow)
				ClearMenuStrip(StatusWindow);

			if(FastWindow)
				ClearMenuStrip(FastWindow);

			switch(Mode)
			{
				case SETITEM_SETCHECK:

					Item -> Flags |= CHECKED;
					break;

				case SETITEM_CLRCHECK:

					Item -> Flags &= ~CHECKED;
					break;

				case SETITEM_ON:

					Item -> Flags |= ITEMENABLED;
					break;

				case SETITEM_OFF:

					Item -> Flags &= ~ITEMENABLED;
					break;
			}

				/* Reattach the menu to the windows. */

			if(Window)
				ResetMenuStrip(Window,Menu);

			if(StatusWindow)
				ResetMenuStrip(StatusWindow,Menu);

			if(FastWindow)
				ResetMenuStrip(FastWindow,Menu);
		}
	}
}

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

LONG
GetFileSize(STRPTR Name)
{
	struct FileInfoBlock	*FileInfo;
	LONG			 FileSize = 0;

	if(FileInfo = (struct FileInfoBlock *)AllocDosObject(DOS_FIB,TAG_DONE))
	{
		BPTR FileLock;

		if(FileLock = Lock(Name,ACCESS_READ))
		{
			if(Examine(FileLock,FileInfo))
			{
				if(FileInfo -> fib_DirEntryType < 0)
					FileSize = FileInfo -> fib_Size;
			}

			UnLock(FileLock);
		}

		FreeDosObject(DOS_FIB,FileInfo);
	}

	return(FileSize);
}

	/* PutDimensionTags(struct Window *Reference,LONG Left,LONG Top,LONG Width,LONG Height):
	 *
	 *	Put back the default values for the requesters to open.
	 */

VOID
PutDimensionTags(struct Window *Reference,LONG Left,LONG Top,LONG Width,LONG Height)
{
	if(Config -> MiscConfig -> RequesterMode != REQUESTERMODE_IGNORE && (!Config -> MiscConfig -> RequesterWidth || !Config -> MiscConfig -> RequesterHeight))
	{
		if(!Reference)
			Reference = Window;

		Config -> MiscConfig -> RequesterLeft	= Left	- Reference -> LeftEdge;
		Config -> MiscConfig -> RequesterTop	= Top	- Reference -> TopEdge;
		Config -> MiscConfig -> RequesterWidth	= Width;
		Config -> MiscConfig -> RequesterHeight	= Height;
	}
}

	/* GetDimensionTags(struct Window *Reference,struct TagItem *Tags):
	 *
	 *	Fills an array of tagitems with the dimensions of an asl requester
	 *	to be opened.
	 */

struct TagItem *
GetDimensionTags(struct Window *Reference,struct TagItem *Tags)
{
	if(Config -> MiscConfig -> RequesterMode == REQUESTERMODE_IGNORE || Config -> MiscConfig -> RequesterWidth < 1 || Config -> MiscConfig -> RequesterHeight < 1)
	{
		STATIC ULONG __aligned Done = TAG_DONE;

		return((struct TagItem *)&Done);
	}
	else
	{
		LONG Left,Top,Width,Height;

		if(!Reference)
			Reference = Window;

		if(Config -> MiscConfig -> RequesterMode == REQUESTERMODE_CENTRE)
		{
			Left	= Reference -> LeftEdge	+ (Reference -> Width - Config -> MiscConfig -> RequesterWidth) / 2;
			Top	= Reference -> TopEdge	+ (Reference -> Height - Config -> MiscConfig -> RequesterHeight) / 2;
		}
		else
		{
			Left	= Config -> MiscConfig -> RequesterLeft	+ Reference -> LeftEdge;
			Top	= Config -> MiscConfig -> RequesterTop	+ Reference -> TopEdge;
		}

		Width	= Config -> MiscConfig -> RequesterWidth;
		Height	= Config -> MiscConfig -> RequesterHeight;

		Tags[0] . ti_Tag	= ASL_LeftEdge;
		Tags[0] . ti_Data	= Left;
		Tags[1] . ti_Tag	= ASL_TopEdge;
		Tags[1] . ti_Data	= Top;
		Tags[2] . ti_Tag	= ASL_Width;
		Tags[2] . ti_Data	= Width;
		Tags[3] . ti_Tag	= ASL_Height;
		Tags[3] . ti_Data	= Height;
		Tags[4] . ti_Tag	= TAG_DONE;

		return(Tags);
	}
}

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

struct FileRequester *
GetFile(struct Window *Parent,STRPTR Title,STRPTR Directory,STRPTR Name,STRPTR Buffer,STRPTR Pattern,BOOL SaveFlag,BOOL MultiSelect,BOOL DirsOnly,STRPTR OKText,BOOL AskWrite)
{
	STATIC UBYTE 		 DirBuffer[MAX_FILENAME_LENGTH],
				 PatternBuffer[60];

	struct FileRequester	*AslFileRequest;
	BOOL			 Result		= FALSE,
				 DefaultPattern	= FALSE;
	LONG			 Flags,ExtFlags = 0;
	LONG			 i,j;

	UBYTE			 OtherTitle[60];
	struct TagItem		 DimensionTags[5];

	if(!Config -> MiscConfig -> ProtectiveMode)
		AskWrite = FALSE;

	if(!Parent)
		Parent = Window;

	for(i = j = 0 ; i < strlen(Title) ; i++)
	{
		if(Title[i] != '_')
			OtherTitle[j++] = Title[i];
	}

	OtherTitle[j] = 0;

	Title = OtherTitle;

	if(DirsOnly)
	{
		ExtFlags |= FIL1F_NOFILES;

		if(Pattern)
			ExtFlags |= FIL1F_MATCHDIRS;
	}

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

	Forbid();

	if(!Directory[0])
	{
		if(!DirBuffer[0])
		{
			UBYTE LocalBuffer[MAX_FILENAME_LENGTH];

			if(!GetCurrentDirName(LocalBuffer,MAX_FILENAME_LENGTH))
				LocalBuffer[0] = 0;

			if(!DirBuffer[0])
				strcpy(DirBuffer,LocalBuffer);
		}

		Directory = DirBuffer;
	}

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

	if(Pattern)
	{
		Flags = FILF_PATGAD;

		if(!Pattern[0])
		{
			DefaultPattern = TRUE;

			if(PatternBuffer[0])
				Pattern = PatternBuffer;
			else
				Pattern = "#?";
		}
		else
		{
			if(!Stricmp(Pattern,"#?") || !Stricmp(Pattern,PatternBuffer))
			{
				DefaultPattern = TRUE;

				Pattern = PatternBuffer;
			}
		}
	}
	else
	{
		Flags = 0;

		Pattern = "#?";
	}

	Permit();

		/* 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;

		/* Provide a standard `Ok' text if none
		 * specified.
		 */

	if(!OKText)
		OKText = (SaveFlag ? LocaleString(MSG_GLOBAL_SAVE_TXT) : LocaleString(MSG_GLOBAL_OPEN_TXT));

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

	if(AslFileRequest = (struct FileRequester *)AllocAslRequestTags(ASL_FileRequest,
		ASLFR_Window,		Parent,
		ASLFR_InitialFile,	Name,
		ASLFR_InitialDrawer,	Directory,
		ASLFR_InitialPattern,	Pattern,
		ASLFR_PositiveText,	OKText,
		ASLFR_TextAttr,		&UserFont,
		ASLFR_PrivateIDCMP,	TRUE,
		ASLFR_Flags1,		Flags | FILF_NEWIDCMP,
		ASLFR_Flags2,		ExtFlags,

		Title ? ASLFR_TitleText : TAG_IGNORE,Title,
	TAG_MORE,GetDimensionTags(Parent,DimensionTags)))
	{
		LT_LockWindow(Parent);

		if(AslRequest(AslFileRequest,NULL))
		{
			PutDimensionTags(NULL,AslFileRequest -> fr_LeftEdge,AslFileRequest -> fr_TopEdge,AslFileRequest -> fr_Width,AslFileRequest -> fr_Height);

			if(!DirsOnly)
			{
				STRPTR FileName;

				if(AslFileRequest -> fr_NumArgs > 1 && AslFileRequest -> fr_ArgList)
					FileName = AslFileRequest -> fr_ArgList -> wa_Name;
				else
					FileName = AslFileRequest -> fr_File;

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

				if(FileName)
				{
						/* Build a legal path/file string. */

					strcpy(Buffer,AslFileRequest -> fr_Drawer);

					AddPart((STRPTR)Buffer,FileName,MAX_FILENAME_LENGTH);

					Result = TRUE;

					Forbid();

					strcpy(DirBuffer,AslFileRequest -> fr_Drawer);

					Permit();
				}
			}
			else
			{
				if(AslFileRequest -> fr_Drawer[0])
				{
					strcpy(Buffer,AslFileRequest -> fr_Drawer);

					Result = TRUE;

					Forbid();

					strcpy(DirBuffer,AslFileRequest -> fr_Drawer);

					Permit();
				}
			}
		}

		LT_UnlockWindow(Parent);
	}
		/* We didn't get a file, no need to keep the
		 * file requester.
		 */

	if(!Result && AslFileRequest)
	{
		FreeAslRequest(AslFileRequest);

		return(NULL);
	}
	else
	{
		if(SaveFlag && AskWrite)
		{
			if(GetFileSize(Buffer))
			{
				if(!ShowRequest(Parent,LocaleString(MSG_GLOBAL_FILE_ALREADY_EXISTS_OVERWRITE_TXT),LocaleString(MSG_GLOBAL_REPLACE_CANCEL_TXT),FilePart(Buffer)))
				{
					FreeAslRequest(AslFileRequest);

					return(NULL);
				}
			}
		}

		Forbid();

		if(DefaultPattern)
			strcpy(PatternBuffer,AslFileRequest -> fr_Pattern);

		Permit();

		return(AslFileRequest);
	}
}

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

LONG __stdargs
ShowRequest(struct Window *Window,STRPTR Text,STRPTR Gadgets,...)
{
	struct EasyStruct	Easy;
	va_list			VarArgs;
	LONG			i,GadgetCount;

	for(i = GadgetCount = 0 ; i < strlen(Gadgets) ; i++)
	{
		if(Gadgets[i] == '|')
			GadgetCount++;
	}

		/* Standard data. */

	Easy . es_StructSize	= sizeof(struct EasyStruct);
	Easy . es_Flags		= NULL;
	Easy . es_Title		= LocaleString(MSG_TERMAUX_TERM_REQUEST_TXT);
	Easy . es_TextFormat	= Text;
	Easy . es_GadgetFormat	= Gadgets;

	if(GadgetCount)
	{
		LONG Result;

		if(GTLayoutBase)
			LT_LockWindow(Window);

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

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

		if(GTLayoutBase)
			LT_UnlockWindow(Window);

		return(Result);
	}
	else
	{
		struct Window *ReqWindow;

		if(GTLayoutBase)
			LT_LockWindow(Window);

		va_start(VarArgs,Gadgets);

		if(ReqWindow = BuildEasyRequestArgs(Window,&Easy,IDCMP_RAWKEY,VarArgs))
		{
			ULONG	IDCMP;
			LONG	Result;

			FOREVER
			{
				WaitPort(ReqWindow -> UserPort);

				IDCMP = NULL;

				Result = SysReqHandler(ReqWindow,&IDCMP,FALSE);

				if(!Result || (Result == -2 && !(IDCMP & IDCMP_RAWKEY)))
					break;
			}

			FreeSysRequest(ReqWindow);
		}

		va_end(VarArgs);

		if(GTLayoutBase)
			LT_UnlockWindow(Window);

		return(0);
	}
}

	/* 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)
{
	Forbid();

	if(Window -> UserPort)
	{
		struct IntuiMessage	*IntuiMessage;
		struct Node		*Successor;

		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();

	LT_DeleteWindowLock(Window);

	CloseWindow(Window);
}

	/* DelayTime(LONG Secs,LONG Micros):
	 *
	 *	Delay for a given period of time.
	 */

VOID
DelayTime(LONG Secs,LONG Micros)
{
	StopTime();

	if(Secs || Micros)
	{
		StartTime(Secs,Micros);

		WaitTime();
	}
}

	/* WaitTime():
	 *
	 *	Wait for a pending time request to finish.
	 */

VOID
WaitTime()
{
	if(TimerRunning)
	{
		TimerRunning = FALSE;

		WaitIO(TimeRequest);
	}
}

	/* StopTime():
	 *
	 *	Stop the running timer.
	 */

VOID
StopTime()
{
	if(TimerRunning)
	{
		if(!CheckIO(TimeRequest))
			AbortIO(TimeRequest);

		WaitIO(TimeRequest);
	}
}

	/* StartTime(LONG Secs,LONG Micros):
	 *
	 *	Start the timer asynchronously.
	 */

VOID
StartTime(LONG Secs,LONG Micros)
{
	StopTime();

	if(Secs || Micros)
	{
		TimeRequest->tr_node.io_Command	= TR_ADDREQUEST;
		TimeRequest->tr_time.tv_secs	= Secs;
		TimeRequest->tr_time.tv_micro	= Micros;

		ClrSignal(SIG_TIMER);

		SendIO(TimeRequest);

		TimerRunning = TRUE;
	}
}

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

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

	Buffer[0] = 0;

		/* Is ENV: present? */

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

		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(STRPTR Name,STRPTR Value):
	 *
	 *	Set the contents of a vanilla AmigaDOS environment
	 *	variable.
	 */

BOOL
SetEnvDOS(STRPTR Name,STRPTR Value)
{
	UBYTE	Buffer[MAX_FILENAME_LENGTH],*Destination;
	LONG	Length;
	BPTR	File,FileLock;
	BOOL	Success = TRUE;
	LONG	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);

						AddProtection(Buffer,FIBF_EXECUTE);
					}
				}
				else
					Success = FALSE;
			}
		}
		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)
{
	if(SomeWindow)
	{
		if(SomeWindow -> WScreen -> LeftEdge > 0)
		{
			if(SomeWindow -> WScreen -> TopEdge > 0)
				MoveScreen(SomeWindow -> WScreen,-SomeWindow -> WScreen -> LeftEdge,-SomeWindow -> WScreen -> TopEdge);
			else
				MoveScreen(SomeWindow -> WScreen,-SomeWindow -> WScreen -> LeftEdge,0);
		}
		else
		{
			if(SomeWindow -> WScreen -> TopEdge > 0)
				MoveScreen(SomeWindow -> WScreen,0,-SomeWindow -> WScreen -> TopEdge);
		}

		ScreenToFront(SomeWindow -> WScreen);

		ActivateWindow(SomeWindow);
	}
}

	/* 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;
	}
}

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

BOOL
LoadMacros(STRPTR Name,struct MacroKeys *Keys)
{
	struct IFFHandle	*Handle;
	BOOL			 Success = FALSE;
	struct StoredProperty	*Prop;
	struct TermInfo		*TermInfo;
	LONG			 Error;

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

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

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

					if(!(Error = StopChunk(Handle,ID_TERM,ID_KEYS)))
					{
						/* Parse the file... */

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

							if(Prop = FindProp(Handle,ID_TERM,ID_VERS))
							{
								TermInfo = (struct TermInfo *)Prop -> sp_Data;

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

								if((TermInfo -> Version > CONFIG_FILE_VERSION) || (TermInfo -> Version == CONFIG_FILE_VERSION && TermInfo -> Revision > CONFIG_FILE_REVISION) || (TermInfo -> Version == 1 && TermInfo -> Revision < 6))
								{
										/* 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
											Error = IoErr();
									}
									else
										Error = ERR_OUTDATED;
								}
								else
								{
									/* 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
										Error = IoErr();
								}
							}
							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;
								else
									Error = IoErr();
							}
						}
					}
				}

				CloseIFF(Handle);
			}

			Close(Handle -> iff_Stream);
		}
		else
			Error = IoErr();

		FreeIFF(Handle);
	}
	else
		Error = ERR_NO_MEM;

	if(Error)
		SetIoErr(Error);

	return(Success);
}

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

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

		/* 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(!(Error = OpenIFF(Handle,IFFF_WRITE)))
			{
					/* Push outmost chunk onto stack. */

				if(!(Error = PushChunk(Handle,ID_TERM,ID_FORM,IFFSIZE_UNKNOWN)))
				{
						/* Add a version identifier. */

					if(!(Error = PushChunk(Handle,0,ID_VERS,IFFSIZE_UNKNOWN)))
					{
						struct TermInfo TermInfo;

						TermInfo . Version	= CONFIG_FILE_VERSION;
						TermInfo . Revision	= CONFIG_FILE_REVISION;

							/* 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(Error = PopChunk(Handle))
								Success = FALSE;
							else
							{
									/* Push the real data chunk on the stack. */

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

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

											/* Pop the data chunk. */

									if(Success)
									{
										if(Error = PopChunk(Handle))
											Success = FALSE;
									}
								}
								else
									Success = FALSE;
							}
						}
						else
						{
							Error = IoErr();

							Success = FALSE;
						}
					}

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

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

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

				CloseIFF(Handle);
			}

				/* Close the DOS handle itself. */

			Close(Handle -> iff_Stream);
		}
		else
			Error = IoErr();

			/* And free the IFF handle. */

		FreeIFF(Handle);
	}
	else
		Error = ERR_NO_MEM;

	if(Success)
		AddProtection(Name,FIBF_EXECUTE);

	if(Error)
		SetIoErr(Error);

	return(Success);
}

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

BOOL
ReadIFFData(STRPTR Name,APTR Data,LONG Size,ULONG Type)
{
	struct IFFHandle	*Handle;
	BOOL			 Success = FALSE;
	struct StoredProperty	*Prop;
	struct TermInfo		*TermInfo;
	LONG			 Error = 0;

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

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

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

					if(!(Error = StopChunk(Handle,ID_TERM,Type)))
					{
						/* Parse the file... */

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

							if(Prop = FindProp(Handle,ID_TERM,ID_VERS))
							{
								TermInfo = (struct TermInfo *)Prop -> sp_Data;

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

								if((TermInfo -> Version > CONFIG_FILE_VERSION) || (TermInfo -> Version == CONFIG_FILE_VERSION && TermInfo -> Revision > CONFIG_FILE_REVISION) || (TermInfo -> Version == 1 && TermInfo -> Revision < 6))
								{
									Error = ERR_OUTDATED;

									Success = FALSE;
								}
								else
								{
									struct ContextNode *Chunk = CurrentChunk(Handle);

									if(Chunk -> cn_Size < Size)
										Size = Chunk -> cn_Size;

									/* 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;
									else
										Error = IoErr();
								}
							}
						}
					}
				}

				CloseIFF(Handle);
			}

			Close(Handle -> iff_Stream);
		}
		else
			Error = IoErr();

		FreeIFF(Handle);
	}

	if(Error)
		SetIoErr(Error);

	return(Success);
}

	/* SplitFileName():
	 *
	 *	Split a full file name into a file and a drawer name.
	 */

VOID
SplitFileName(STRPTR FullName,STRPTR *FileName,STRPTR DrawerName)
{
	if(FilePart(FullName) == FullName)
	{
		*DrawerName	= 0;
		*FileName	= FullName;
	}
	else
	{
		STRPTR Dummy;

		strcpy(DrawerName,FullName);

		Dummy = PathPart(DrawerName);

		*Dummy = 0;

		*FileName = FilePart(FullName);
	}
}

	/* CreateList():
	 *
	 *	Create a small, empty list.
	 */

struct List *
CreateList()
{
	struct List *List;

	if(List = (struct List *)AllocVecPooled(sizeof(struct MinList),MEMF_ANY))
		NewList(List);

	return(List);
}

	/* DeleteList():
	 *
	 *	Free the contents of a list and the list itself.
	 */

VOID
DeleteList(struct List *List)
{
	if(List)
	{
		struct Node	*Node,
				*Next;

		for(Node = List -> lh_Head ; Next = Node -> ln_Succ ; Node = Next)
			FreeVecPooled(Node);

		FreeVecPooled(List);
	}
}

	/* BackfillRoutine():
	 *
	 *	Window layer backfill routine.
	 */

VOID __saveds __asm
BackfillRoutine(REG(a0) struct Hook *Hook,REG(a1) LayerMsg *Bounds,REG(a2) struct RastPort *RPort)
{
	struct RastPort RastPort;

	InitRastPort(&RastPort);

	RastPort.BitMap = RPort->BitMap;

	RPort = &RastPort;

	SetAPen(RPort,Pens[BACKGROUNDPEN]);

	RectFill(RPort,Bounds->Bounds.MinX,Bounds->Bounds.MinY,Bounds->Bounds.MaxX,Bounds->Bounds.MaxY);
}

struct NameSegment
{
	UBYTE	String[63],
		Separator;
};

STATIC STRPTR
FindChar(STRPTR Template,LONG c)
{
	while(*Template)
	{
		if(*Template == c)
			return(Template);
		else
			Template++;
	}

	return(NULL);
}

STATIC UBYTE *
GetNameSegment(struct NameSegment *NameSegment,UBYTE *cp,LONG i)
{
	UBYTE *xp = NameSegment[i] . String;

	while (*cp && !FindChar("._+-,@~=",*cp))
		*xp++ = *cp++;

	*xp = '\0';

	NameSegment[i] . Separator = *cp;

	if (*cp)
		cp++;

	if(NameSegment[i] . String[0] || NameSegment[i] . Separator)
		return(cp);
	else
		return(NULL);
}

STATIC VOID
CopyNameSegment(struct NameSegment *NameSegment,UBYTE *Destination,LONG Zap)
{
	UBYTE *cp = Destination,*xp;

	for(xp = NameSegment[0] . String ; *xp ; )
		*cp++ = *xp++;

	if(NameSegment[0] . Separator)
	{
		register LONG i;

		*cp++ = NameSegment[0] . Separator;

		for(i = Zap + 1 ; ; i++)
		{
			for(xp = NameSegment[i] . String ; *xp ; )
				*cp++ = *xp++;

			if(NameSegment[i] . Separator)
				*cp++ = NameSegment[i] . Separator;
			else
			{
				if(!NameSegment[i] . String[0])
					break;
			}
		}
	}

	*cp = 0;
}

	/* ShrinkName():
	 *
	 *	Shrink a file name down to a number of characters, if
	 *	possible preserving the structure of the name. Algorithm
	 *	courtesy of the "shrink" program by Simon Brown,
	 *	Edinburgh University.
	 */

UBYTE *
ShrinkName(const UBYTE *Source,UBYTE *Destination,LONG MaxLength,BOOL FixSuffix)
{
	#define MAXSEGS 10

	struct NameSegment NameSegment[MAXSEGS];

	LONG i,NumSegments,SuffixLength,Remainder,Delete,Total,Zap = 0;
	UBYTE *OldPtr = (UBYTE *)Source;

	for(i = 0 ; i < MAXSEGS && (OldPtr = GetNameSegment(NameSegment,OldPtr,i)) ; i++);

	if(i < MAXSEGS)
	{
		NumSegments = i - 1;

		if((NumSegments * 2) < MaxLength)
		{
			SuffixLength = strlen(NameSegment[NumSegments] . String);

			if(SuffixLength > MaxLength - NumSegments - 1)
			{
				SuffixLength = MaxLength - (2 * NumSegments) - 1;

				NameSegment[NumSegments] . String[SuffixLength - 1] = 0;
			}
			else
			{
				if (SuffixLength + NumSegments > MaxLength-NumSegments-1)
					Zap = NumSegments - (MaxLength-SuffixLength)/2;
			}

			if(NumSegments >= 1)
			{
				for(i = Zap + 1 ; i <= NumSegments ; i++)
				{
					if(NameSegment[i] . Separator)
						SuffixLength++;
				}

				if(NameSegment[0] . Separator)
					SuffixLength++;

				Remainder = MaxLength - SuffixLength;

				Delete = Remainder / NumSegments;

				Total = SuffixLength;

				for(i = Zap + 1 ; i < NumSegments ; i++)
				{
					NameSegment[i] . String[Delete] = 0;

					Total += Delete;
				}

				NameSegment[0] . String[MaxLength - Total] = 0;
			}

			CopyNameSegment(NameSegment,Destination,Zap);
		}
		else
			strcpy(Destination,Source);
	}
	else
		strcpy(Destination,Source);

	if(FixSuffix)
	{
		BOOL	GotDot	= FALSE;
		LONG	Len	= strlen(Destination),Dots;

		for(i = Dots = 0 ; i < Len ; i++)
		{
			if(Destination[i] == '.')
				Dots++;
		}

		if(!Dots)
		{
			if(Len < 4)
				strcat(Destination,".___");
			else
				Destination[Len - 4] = '.';
		}

		for(i = Len - 1 ; i >= 0 ; i--)
		{
			if(Destination[i] == '.')
			{
				if(GotDot)
					Destination[i] = '_';
				else
					GotDot = TRUE;
			}

			if(Destination[i] == '\\')
				Destination[i] = '-';
		}
	}

	return(Destination);
}

	/* BuildFontName(STRPTR Destination,const STRPTR Name,LONG Size):
	 *
	 *	Build a font name and size string from given
	 *	information (raw name and size).
	 */

VOID
BuildFontName(STRPTR Destination,const STRPTR Name,LONG Size)
{
	UBYTE	LocalBuffer[50];
	LONG	Len;

	strcpy(LocalBuffer,FilePart((STRPTR)Name));

	Len = strlen(LocalBuffer);

	if(Len > 5)
	{
		if(!Stricmp(&LocalBuffer[Len - 5],".font"))
			LocalBuffer[Len - 5] = 0;
	}

	SPrintf(Destination,"%s %ld",LocalBuffer,Size);
}

	/* CreateMenuGlyphs():
	 *
	 *	Create scaled glyphs for pull-down menus.
	 */

BOOL
CreateMenuGlyphs(struct Screen *Screen,struct DrawInfo *DrawInfo,struct Image **AmigaPtr,struct Image **CheckPtr)
{
	struct Image	*AmigaGlyph,
			*CheckGlyph;
	LONG		 AspectX,AspectY,
			 Size,FontHeight;

	FontHeight = DrawInfo -> dri_Font -> tf_Baseline + 2;

	if(Screen -> Flags & SCREENHIRES)
		Size = SYSISIZE_MEDRES;
	else
		Size = SYSISIZE_LOWRES;

	AspectX = DrawInfo -> dri_Resolution . X;
	AspectY = DrawInfo -> dri_Resolution . Y;

	if(AmigaGlyph = NewObject(NULL,SYSICLASS,
		SYSIA_DrawInfo,	DrawInfo,
		SYSIA_Size,	Size,
		SYSIA_Which,	AMIGAKEY,
		IA_Left,	0,
		LA_Top,		0,
		IA_Width,	(FontHeight * 3 * AspectY) / (2 * AspectX),
		IA_Height,	FontHeight,
	TAG_DONE))
	{
		if(!(CheckGlyph = NewObject(NULL,SYSICLASS,
			SYSIA_DrawInfo,	DrawInfo,
			SYSIA_Size,	Size,
			SYSIA_Which,	MENUCHECK,
			IA_Left,	0,
			LA_Top,		0,
			IA_Width,	(FontHeight * AspectY) / AspectX,
			IA_Height,	FontHeight,
		TAG_DONE)))
		{
			DisposeObject(AmigaGlyph);

			AmigaGlyph = NULL;
		}
	}

	if(AmigaGlyph && CheckGlyph)
	{
		*AmigaPtr = AmigaGlyph;
		*CheckPtr = CheckGlyph;

		return(TRUE);
	}
	else
		return(FALSE);
}

	/* FixName(STRPTR Name):
	 *
	 *	Build a correct AmigaDOS filename.
	 */

VOID
FixName(STRPTR Name)
{
	LONG	NameLen	= strlen(Name),
		i;

		/* Replace special characters. */

	for(i = 0 ; i < NameLen ; i++)
	{
		switch(Name[i])
		{
			case ' ':

				Name[i] = '_';
				break;

			case ':':

				Name[i] = '.';
				break;

			case '/':

				Name[i] = '\\';
				break;

			case '\"':

				Name[i] = '\'';
				break;
		}
	}

		/* Truncate the name. */

	if(NameLen > 31)
		Name[31] = 0;
}

	/* ShowError(struct Window *Window,LONG Primary,LONG Secondary,STRPTR String):
	 *
	 *	Display an error message, in human readable form if possible.
	 */

VOID
ShowError(struct Window *Window,LONG Primary,LONG Secondary,STRPTR String)
{
	STATIC LONG LocalErrors[][2] =
	{
		ERR_SAVE_ERROR,			MSG_ERR_COULD_NOT_SAVE_FILE_TXT,
		ERR_LOAD_ERROR,			MSG_ERR_COULD_NOT_LOAD_FILE_TXT,
		ERR_NO_MEM,			MSG_ERR_NO_MEM_TXT,
		ERR_OUTDATED,			MSG_ERR_OUTDATED_TXT,
		ERR_EXECUTE_ERROR,		MSG_ERR_COULD_NOT_EXECUTE_PROGRAM_TXT,

		ERR_FILE_NOT_FOUND,		MSG_VERIFY_NO_FILE_TXT,
		ERR_DRAWER_NOT_FOUND,		MSG_VERIFY_DRAWER_NOT_FOUND_TXT,
		ERR_PROGRAM_NOT_FOUND,		MSG_VERIFY_NO_PROGRAM_TXT,
		ERR_NOT_A_FILE,			MSG_VERIFY_DRAWER_NOT_A_FILE_TXT,
		ERR_NOT_A_DRAWER,		MSG_VERIFY_FILE_NOT_A_DRAWER_TXT,

		IFFERR_NOMEM,			MSG_IFFERR_NOMEM_TXT,
		IFFERR_READ,			MSG_IFFERR_READ_TXT,
		IFFERR_WRITE,			MSG_IFFERR_WRITE_TXT,
		IFFERR_SEEK,			MSG_IFFERR_SEEK_TXT,
		IFFERR_MANGLED,			MSG_IFFERR_MANGLED_TXT,
		IFFERR_NOTIFF,			MSG_IFFERR_NOTIFF_TXT,

		0,				0
	};

	STRPTR	PrimaryError	= NULL,
		SecondaryError	= NULL;

	if(Primary)
	{
		LONG i;

		for(i = 0 ; LocalErrors[i][0] ; i++)
		{
			if(LocalErrors[i][0] == Primary)
			{
				PrimaryError = LocaleString(LocalErrors[i][1]);

				break;
			}
		}

		if(!PrimaryError)
		{
			STATIC UBYTE Buffer[256];

			Fault(Primary,"",Buffer,256);

			PrimaryError = Buffer + 2;
		}
	}

	if(Secondary)
	{
		LONG i;

		for(i = 0 ; LocalErrors[i][0] ; i++)
		{
			if(LocalErrors[i][0] == Secondary)
			{
				SecondaryError = LocaleString(LocalErrors[i][1]);

				break;
			}
		}

		if(!SecondaryError)
		{
			STATIC UBYTE Buffer[256];

			Fault(Secondary,"",Buffer,256);

			SecondaryError = Buffer + 2;
		}
	}

	if(PrimaryError)
	{
		if(String)
			ShowRequest(Window,PrimaryError,LocaleString(MSG_GLOBAL_CONTINUE_TXT),String,SecondaryError);
		else
			ShowRequest(Window,PrimaryError,LocaleString(MSG_GLOBAL_CONTINUE_TXT),SecondaryError);
	}
}

struct RendezvousData * __saveds __asm
RendezvousLogin(REG(a0) struct MsgPort *ReadPort,REG(a1) struct MsgPort *WritePort,REG(a2) struct TagItem *TagList)
{
	struct RendezvousData *Data;

	if(Data = (struct RendezvousData *)AllocVecPooled(sizeof(struct RendezvousData),MEMF_ANY | MEMF_CLEAR))
	{
		struct DataMsg Msg;

		InitMsgItem(&Msg,TaskDestructor);

		Msg . Type = DATAMSGTYPE_RENDEZVOUS;
		Msg . Data = (UBYTE *)FindTask(NULL);

		Forbid();

		ClrSignal(SIGBREAKF_CTRL_F);

		PutMsgItem(SpecialQueue,(struct MsgItem *)&Msg);

		Wait(SIGBREAKF_CTRL_F);

		Permit();

		if(ReadRequest && WriteRequest)
		{
			struct List *UploadList;

			CopyMem(ReadRequest,&Data -> rd_ReadRequest,sizeof(struct IOExtSer));

			Data -> rd_ReadRequest . IOSer . io_Message . mn_ReplyPort = ReadPort;

			CopyMem(WriteRequest,&Data -> rd_WriteRequest,sizeof(struct IOExtSer));

			Data -> rd_WriteRequest . IOSer . io_Message . mn_ReplyPort = WritePort;

			if(Window)
				Data -> rd_Screen = Window -> WScreen;

			NewList(&Data -> rd_UploadList);
			NewList(&Data -> rd_DownloadList);
			NewList(&Data -> rd_SentList);
			NewList(&Data -> rd_ReceivedList);

			if(UploadList = GetUploadList())
			{
				struct Node *Node,*Next;

				for(Node = UploadList -> lh_Head ; Next = Node -> ln_Succ ; Node = Next)
					AddTail(&Data -> rd_UploadList,Node);

				FreeVecPooled(UploadList);
			}

			Data -> rd_SendPath	= Config -> PathConfig -> BinaryUploadPath;
			Data -> rd_ReceivePath	= Config -> PathConfig -> BinaryDownloadPath;
			Data -> rd_Options	= "";
		}
		else
		{
			FreeVecPooled(Data);

			Data = NULL;
		}
	}

	return(Data);
}

VOID __saveds __asm
RendezvousLogoff(REG(a0) struct RendezvousData *Data)
{
	if(Data)
	{
		struct Node *Node,*Next;

		for(Node = Data -> rd_UploadList . lh_Head ; Next = Node -> ln_Succ ; Node = Next)
			FreeVecPooled(Node);

		for(Node = Data -> rd_DownloadList . lh_Head ; Next = Node -> ln_Succ ; Node = Next)
			FreeVecPooled(Node);

		for(Node = Data -> rd_SentList . lh_Head ; Next = Node -> ln_Succ ; Node = Next)
			FreeVecPooled(Node);

		for(Node = Data -> rd_ReceivedList . lh_Head ; Next = Node -> ln_Succ ; Node = Next)
			FreeVecPooled(Node);

		FreeVecPooled(Data);

		Signal(ThisProcess,SIG_HANDSHAKE);
	}
}

struct Node * __saveds __asm
RendezvousNewNode(REG(a0) STRPTR Name)
{
	if(Name)
	{
		LONG Len;

		if(Len = strlen(Name))
		{
			struct Node *Node;

			if(Node = (struct Node *)AllocVecPooled(sizeof(struct Node) + Len + 1,MEMF_ANY))
			{
				memset(Node,0,sizeof(struct Node));

				strcpy(Node -> ln_Name = (STRPTR)(Node + 1),Name);

				return(Node);
			}
		}
	}

	return(NULL);
}

struct List *
BuildModeList(LONG *Index,ULONG DisplayMode,ULONG (* __asm ModeFilter)(REG(d0) ULONG ID,REG(a0) APTR UserData),APTR UserData)
{
	struct List	*List;
	LONG		 Count = 0;

	*Index = 0;

	if(List = (struct List *)AllocVecPooled(sizeof(struct MinList),MEMF_ANY))
	{
		struct DisplayInfo	DisplayInfo;
		ULONG			SomeMode = INVALID_ID;

		NewList(List);

		while((SomeMode = NextDisplayInfo(SomeMode)) != INVALID_ID)
		{
			if(GetDisplayInfoData(NULL,(APTR)&DisplayInfo,sizeof(struct DisplayInfo),DTAG_DISP,SomeMode))
			{
				if((DisplayInfo . PropertyFlags & DIPF_IS_WB) && !(DisplayInfo . NotAvailable & ~DI_AVAIL_NOTWITHGENLOCK))
				{
					STRPTR Name;

					if(ModeFilter)
					{
						if(!(*ModeFilter)(SomeMode,UserData))
							continue;
					}

					if(Name = GetModeName(SomeMode))
					{
						struct ModeNode	*ModeNode;

						if(ModeNode = (struct ModeNode *)AllocVecPooled(sizeof(struct ModeNode) + strlen(Name) + 1,MEMF_ANY))
						{
							ModeNode -> VanillaNode . ln_Name = (STRPTR)(ModeNode + 1);

							strcpy(ModeNode -> VanillaNode . ln_Name,Name);

							ModeNode -> DisplayID = SomeMode;

							AddTail(List,ModeNode);

							Count++;
						}
					}
				}
			}
		}
	}

	if(Count)
	{
		struct ModeNode	*Node,
				*Next;

		for(Node = (struct ModeNode *)List -> lh_Head ; Node -> VanillaNode . ln_Succ ; Node = (struct ModeNode *)Node -> VanillaNode . ln_Succ)
		{
			if(!(Node -> DisplayID & MONITOR_ID_MASK))
			{
				for(Next = (struct ModeNode *)List -> lh_Head ; Next -> VanillaNode . ln_Succ ; Next = (struct ModeNode *)Next -> VanillaNode . ln_Succ)
				{
					if((Node -> DisplayID & ~MONITOR_ID_MASK) == (Next -> DisplayID & ~MONITOR_ID_MASK) && Next != Node)
						Node -> DisplayID = INVALID_ID;
				}
			}

			for(Next = (struct ModeNode *)List -> lh_Head ; Next -> VanillaNode . ln_Succ ; Next = (struct ModeNode *)Next -> VanillaNode . ln_Succ)
			{
				if(Node -> DisplayID == Next -> DisplayID && Next != Node)
					Next -> DisplayID = INVALID_ID;
			}
		}

		for(Node = (struct ModeNode *)List -> lh_Head ; Next = (struct ModeNode *)Node -> VanillaNode . ln_Succ ; Node = Next)
		{
			if(Node -> DisplayID == INVALID_ID)
			{
				Count--;

				Remove(Node);

				FreeVecPooled(Node);
			}
		}
	}

	if(Count)
	{
		struct ModeNode	*Node,
				*Next;

		for(Count = 0, Node = (struct ModeNode *)List -> lh_Head ; Next = (struct ModeNode *)Node -> VanillaNode . ln_Succ ; Node = Next)
		{
			if(Node -> DisplayID == DisplayMode)
			{
				*Index = Count;

				break;
			}
		}

		return(List);
	}
	else
	{
		DeleteList(List);

		return(NULL);
	}
}

	/* IsAssign(STRPTR Name):
	 *
	 *	Does a name refer to an assignment?
	 */

BOOL
IsAssign(STRPTR Name)
{
	LONG NameLen	= strlen(Name) - 1;
	BOOL Result	= FALSE;

		/* Does it end with a colon? */

	if(Name[NameLen] == ':')
	{
		struct DosList *DosList;

			/* Lock the list of assignments for reading. */

		DosList = AttemptLockDosList(LDF_ASSIGNS | LDF_READ);

			/* Make sure the v37 bug doesn't catch us. */

		if(((ULONG)DosList) > 1)
		{
			STRPTR AssignName;

				/* Scan the list... */

			while(DosList = NextDosEntry(DosList,LDF_ASSIGNS))
			{
					/* Convert the name from icky
					 * BCPL to `C' style string.
					 */

				AssignName = (STRPTR)BADDR(DosList -> dol_Name);

					/* Does the name length match? */

				if(AssignName[0] == NameLen)
				{
						/* Does the name itself match? */

					if(!Strnicmp(&AssignName[1],Name,NameLen))
					{
						Result = TRUE;

						break;
					}
				}
			}

				/* Unlock the list of assignments. */

			UnLockDosList(LDF_ASSIGNS | LDF_READ);
		}
	}

		/* Return the result. */

	return(Result);
}

	/* LockInAssign(BPTR TheLock,STRPTR TheAssignment):
	 *
	 *	Check if a file lock is part of an assignment.
	 */

BOOL
LockInAssign(BPTR TheLock,STRPTR TheAssignment)
{
	struct DevProc	*DevProc	= NULL;
	struct MsgPort	*FileSysTask	= GetFileSysTask();
	BOOL		 Result		= FALSE;


		/* Loop until all assignments are
		 * processed.
		 */

	do
	{
			/* Get the default filesystem task
			 * in case we stumble upon NULL
			 * directory locks.
			 */

		if(DevProc = GetDeviceProc(TheAssignment,DevProc))
		{
				/* Set the default filesystem task. */

			SetFileSysTask(DevProc -> dvp_Port);

				/* Is the lock on the list? */

			if(SameLock(DevProc -> dvp_Lock,TheLock) == LOCK_SAME)
				Result = TRUE;
		}
		else
			break;
	}
	while(DevProc && (DevProc -> dvp_Flags & DVPF_ASSIGN) && !Result);

		/* Reset the default filesystem task. */

	SetFileSysTask(FileSysTask);

		/* Free device process data. */

	if(DevProc)
		FreeDeviceProc(DevProc);

	return(Result);
}

	/* DeleteBitMap(struct BitMap *BitMap):
	 *
	 *	Delete a bitmap created by CreateBitMap().
	 */

VOID
DeleteBitMap(struct BitMap *BitMap)
{
	if(BitMap)
	{
		WaitBlit();

		if(Kick30)
			FreeBitMap(BitMap);
		else
		{
			LONG i;

			for(i = 0 ; i < BitMap -> Depth ; i++)
			{
				if(BitMap -> Planes[i])
					FreeVec(BitMap -> Planes[i]);
			}

			FreeVecPooled(BitMap);
		}
	}
}

	/* CreateBitMap(ULONG Width,ULONG Height,ULONG Depth,ULONG Flags,struct BitMap *Friend):
	 *
	 *	Create a new bitmap with given properties.
	 */

struct BitMap *
CreateBitMap(ULONG Width,ULONG Height,ULONG Depth,ULONG Flags,struct BitMap *Friend)
{
	if(Kick30)
		return(AllocBitMap(Width,Height,Depth,Flags,Friend));
	else
	{
		struct BitMap	*BitMap;
		LONG		 Plus;

		if(Depth > 8)
			Plus = (Depth - 8) * sizeof(PLANEPTR);
		else
			Plus = 0;

		if(BitMap = (struct BitMap *)AllocVecPooled(sizeof(struct BitMap) + Plus,MEMF_CLEAR))
		{
			LONG i,PageSize;

			InitBitMap(BitMap,Depth,Width,Height);

			PageSize = BitMap -> BytesPerRow * BitMap -> Rows;

			for(i = 0 ; i < BitMap -> Depth ; i++)
			{
				if(!(BitMap -> Planes[i] = (PLANEPTR)AllocVec(PageSize,MEMF_CHIP)))
				{
					LONG j;

					for(j = 0 ; j < i ; j++)
						FreeVec(BitMap -> Planes[j]);

					FreeVecPooled(BitMap);

					return(NULL);
				}
			}

			if(Flags & BMF_CLEAR)
				BltBitMap(BitMap,0,0,BitMap,0,0,Width,Height,0x00,(1 << Depth) - 1,NULL);
		}

		return(BitMap);
	}
}

	/* LaunchCommand(STRPTR Command):
	 *
	 *	Launch a command, try to figure out if it's an ARexx
	 *	command, a plain batch file or a program and act
	 *	accordingly.
	 */

LONG
LaunchCommand(STRPTR Command)
{
	UBYTE		 LocalBuffer[MAX_FILENAME_LENGTH];
	BPTR		 Stream,
			 OutputStream;
	struct MsgPort	*OldConsoleTask;
	BOOL		 IsScript	= FALSE,
			 IsRexx		= FALSE;
	LONG		 Result;
	BPTR		 CommandFile;
	UBYTE		 NameBuffer[MAX_FILENAME_LENGTH];
	BPTR		 FileLock;
	STRPTR		 OtherName;
	LONG		 i;

		/* Chop off the arguments. */

	CopyMem(Command,NameBuffer,MAX_FILENAME_LENGTH - 1);

	NameBuffer[MAX_FILENAME_LENGTH - 1] = 0;

	for(i = 0 ; i < MAX_FILENAME_LENGTH ; i++)
	{
		if(NameBuffer[i] == ' ')
		{
			NameBuffer[i] = 0;
			break;
		}
	}

		/* Now do something useful. Check if the command
		 * could be an ARexx script.
		 */

	if(CommandFile = Open(NameBuffer,MODE_OLDFILE))
	{
		LONG Len;

			/* 256 bytes may be not be enough, but then
			 * we're only guessing.
			 */

		if((Len = Read(CommandFile,LocalBuffer,MAX_FILENAME_LENGTH)) > 0)
		{
			register LONG	c;
			LONG		i;

			for(i = 0 ; i < Len - 1 ; i++)
			{
				c = LocalBuffer[i];

					/* Stop on invalid characters. */

				if((c < ' ' && c != '\r' && c != '\n' && c != '\a') || (c >= 127 && c < 160))
					break;
				else
				{
						/* Looks like the typical
						 * introductory comment line.
						 */

					if(c == '/' && LocalBuffer[i + 1] == '*')
					{
						IsRexx = TRUE;
						break;
					}
				}
			}
		}

		Close(CommandFile);
	}

		/* Ok, second check. Does it have the script bit set? */

	if(FileLock = Lock(NameBuffer,ACCESS_READ))
	{
		struct FileInfoBlock __aligned FileInfo;

		if(Examine(FileLock,&FileInfo))
		{
			if(FileInfo . fib_Protection & FIBF_SCRIPT)
				IsScript = TRUE;
		}

		UnLock(FileLock);
	}

		/* If it's an ARexx command, launch it. */

	if(IsRexx)
	{
		SendARexxCommand(Command,TRUE);

		return(0);
	}

		/* If it's a script command, prepend the "Execute "
		 * command. It's not perfect but it should work.
		 */

	if(IsScript)
	{
		if(OtherName = AllocVecPooled(strlen("Execute ") + strlen(Command) + 1,MEMF_ANY))
		{
			strcpy(OtherName,"Execute ");
			strcat(OtherName,Command);

			Command = OtherName;
		}
	}
	else
		OtherName = NULL;

	if(WindowName[0])
	{
		UBYTE LocalName[MAXPUBSCREENNAME + 1];

		if(Window)
		{
			if(!GetPubScreenName(Window -> WScreen,LocalName))
				LocalName[0] = 0;
		}

		if(LocalName[0])
		{
			SPrintf(LocalBuffer,WindowName,LocalName);

			Stream = Open(LocalBuffer,MODE_NEWFILE);
		}
		else
			Stream = Open(WindowName,MODE_NEWFILE);
	}
	else
		Stream = NULL;

	if(GoodStream(Stream))
	{
		OldConsoleTask = GetConsoleTask();

		SetConsoleTask(((struct FileHandle *)BADDR(Stream)) -> fh_Type);

		OutputStream = Open("CONSOLE:",MODE_NEWFILE);
	}
	else
		OutputStream = NULL;

	if(Stream && OutputStream)
	{
		Result = SystemTags(Command,
			SYS_Input,	Stream,
			SYS_Output,	OutputStream,
			SYS_UserShell,	TRUE,
		TAG_DONE);
	}
	else
	{
		Result = SystemTags(Command,
			SYS_UserShell,	TRUE,
		TAG_DONE);
	}

	if(OutputStream)
	{
		SetConsoleTask(OldConsoleTask);
		Close(OutputStream);
	}

	if(Stream)
		Close(Stream);

	if(OtherName)
		FreeVecPooled(OtherName);

	return(Result);
}

	/* AskDial(struct Window *Parent):
	 *
	 *	This is called when the user is about to start dialing. If the
	 *	dial button/menu item is available with the protective mode
	 *	enabled, a message will be displayed, asking if the line
	 *	should be hung up before proceeding.
	 */

BOOL
AskDial(struct Window *Parent)
{
	if(!DialItemsAvailable)
	{
		if(ShowRequest(Parent,LocaleString(MSG_CANNOT_DIAL_BECAUSE_TXT),LocaleString(MSG_DIAL_CANCEL_TXT)))
			FullHangup(TRUE);
		else
			return(FALSE);
	}

	return(TRUE);
}

	/* Strcoll(const STRPTR a1,const STRPTR b1):
	 *
	 *	A working strcoll() like routine, which does string comparison
	 *	ignoring case and accents, but taking special national characters
	 *	into account. I am certain about how the german Umlauts should
	 *	be treated, but the treatment for Æ, Þ, Ð and ÿ is pure guesswork...
	 */

LONG
Strcoll(STRPTR a1,STRPTR b1)
{
	enum {	CHAR_any,
		CHAR_ae,
		CHAR_oe,
		CHAR_ue,
		CHAR_ss,
		CHAR_ij
	};

#ifdef __SASC
#pragma msg 62 ignore push
#endif

	STATIC UBYTE const noaccent[256] =
	{
		  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
		 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
		' ','!', 34,'#','$','%','&', 39,'(',')','*','+',',','-','.','/',
		'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
		'@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
		'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',
		'`','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
		'P','Q','R','S','T','U','V','W','X','Y','Z','{','|','}','~',127,
		128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
		144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,
		' ','¡','¢','£','¤','¥','¦','§','¨','©','ª','«','¬','­','®','¯',
		'°','±','²','³','´','µ','¶','·','¸','¹','º','»','¼','½','¾','¿',
		'A','A','A','A','A','A','A','C','E','E','E','E','I','I','I','I',
		'D','N','O','O','O','O','O','×','O','U','U','U','U','Y','B','S',
		'A','A','A','A','A','A','A','C','E','E','E','E','I','I','I','I',
		'D','N','O','O','O','O','O','×','O','U','U','U','U','Y','B','I'
	};

#ifdef __SASC
#pragma msg 62 pop
#endif

	STATIC UBYTE const types[256] =
	{
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
		0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,2,0,0,0,0,0,3,0,0,4,
		0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,
		0,0,0,0,0,0,2,0,0,0,0,0,3,0,0,5
	};

	STRPTR a = (const STRPTR)a1;
	STRPTR b = (const STRPTR)b1;

	LONG mul = 1;

loop:

	while(*a && *b && noaccent[*a] == noaccent[*b]) { a++; b++; }

	if(*a || *b)
	{
		LONG c = noaccent[*a],d = noaccent[*b];

		if((types[c] && types[d]) || (!types[c] && !types[d]))
			return(mul * ((LONG)noaccent[c] - (LONG)noaccent[d]));
		else
		{
			if(types[c] == CHAR_any)
			{
				register STRPTR t;
				register LONG e;

				e = c;
				c = d;
				d = e;

				t = a;
				a = b;
				b = t;

				mul = -mul;
			}

			if(d && noaccent[c] == d)
			{
				switch(types[c])
				{
					case CHAR_ss:

						c = 'S';
						break;

					case CHAR_ij:

						c = 'J';
						break;

					default:

						c = 'E';
						break;
				}

				a++;b++;

				d = noaccent[*b];

				b++;

				if(c == d)
					goto loop;
			}

			return(mul * ((LONG)noaccent[c] - (LONG)noaccent[d]));
		}
	}

	return(0);
}

	/* LaunchProcess(STRPTR Name,VOID (*Entry)(VOID),BPTR Stream):
	 *
	 *	Launch a new process from given entry point, with given
	 *	name and I/O stream.
	 */

struct Process *
LaunchProcess(STRPTR Name,VOID (*Entry)(VOID),BPTR Stream)
{
	struct MsgPort *ConsoleTask;

	if(Stream && GoodStream(Stream))
		ConsoleTask = ((struct FileHandle *)BADDR(Stream)) -> fh_Type;
	else
		ConsoleTask = NULL;

	return(CreateNewProcTags(
		NP_Entry,	Entry,
		NP_StackSize,	8000,
		NP_Name,	Name,
		NP_Cli,		TRUE,
		NP_ConsoleTask,	ConsoleTask,

		ConsoleTask ? TAG_IGNORE : TAG_DONE,0,

		NP_Output,	ConsoleTask ? NULL : Stream,

		ConsoleTask ? NP_Input : TAG_IGNORE,Stream,
	TAG_DONE));
}

	/* String2Boolean(STRPTR String):
	 *
	 *	Compare string contents with table contents,
	 *	map the string to a boolean value.
	 */

BOOL
String2Boolean(STRPTR String)
{
	STATIC STRPTR TrueOptions[] =
	{
		"ON",
		"TRUE",
		"T",
		"YES",
		"Y",
		"1"
	};

	LONG i;

	for(i = 0 ; i < NumElements(TrueOptions) ; i++)
	{
		if(!Stricmp(String,TrueOptions[i]))
			return(TRUE);
	}

	return(FALSE);
}

	/* SendMessageGetReply(struct MsgPort *Port,struct Message *Message):
	 *
	 *	Send a message to a given MsgPort and wait for
	 *	the reply.
	 */

VOID
SendMessageGetReply(struct MsgPort *Port,struct Message *Message)
{
	struct MsgPort LocalPort;
	struct MsgPort *ReplyPort;

	ReplyPort = Message->mn_ReplyPort;

	InitSinglePort(&LocalPort);

	Message->mn_ReplyPort = &LocalPort;

	SetSignal(0,SIGF_SINGLE);

	PutMsg(Port,Message);
	WaitPort(&LocalPort);
	GetMsg(&LocalPort);

	Message->mn_ReplyPort = ReplyPort;
}

	/* SetOnlineState(BOOL IsOnline):
	 *
	 *	Set the current online state.
	 */

VOID
SetOnlineState(BOOL IsOnline)
{
	ObtainSemaphore(&OnlineSemaphore);

	WasOnline	= Online;
	Online		= IsOnline;

	ReleaseSemaphore(&OnlineSemaphore);
}
