/* $Revision Header * Header built automatically - do not edit! *************
 *
 *	(C) Copyright 1990 by Olaf Barthel & MXM
 *
 *	Name .....: FracBlank.c
 *	Created ..: Friday 18-Jun-91 15:28
 *	Revision .: 3
 *
 *	Date            Author          Comment
 *	=========       ========        ====================
 *	29-Jun-91	Olsen		Added VBlank interrupt server.
 *	21-Jun-91	Olsen		Optimizations, cleanups.
 *	18-Jun-91	Olsen		Created this file!
 *
 * $Revision Header ********************************************************/

	/* Include the specific math pragmas/header files here (is there
	 * any way to figure this out by taking a look at predefined
	 * compiler symbols?).
	 */

#ifdef MATH_FFP
#include <clib/mathtrans_protos.h>
#include <proto/mathtrans.h>
#include <mffp.h>
#else
#include <m68881.h>
#endif	/* MATH_FFP */

#include <math.h>

	/* sin -45° = cos -45° (saves precious calculation time). */

#define deg45	-0.707106781

	/* Hotkey IDs. */

enum	{	POP_WINDOW,BLANK_SCREEN };

	/* Gadget IDs. */

enum	{	GAD_SCREENTIMEOUT,GAD_PATTERNCHANGE,GAD_HOTKEY,
		GAD_BLANKSCREEN,GAD_HIDE,GAD_QUIT };

	/* Dimensions of the control panel. */

#define WIDTH	308
#define HEIGHT	87

	/* Sprite blanker interface data. */

struct BlankerInfo
{
	struct Task	*RingBack;
	BYTE		 Signal;
	BYTE		 Enabled;
};

	/* Program revision tag. */

STATIC const UBYTE * const VersTag = "\0$VER: FracBlank 1.4 (23.7.91)";

	/* Shared library identifiers. */

extern struct ExecBase	*SysBase;
struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
struct Library		*CxBase,
			*IconBase,
			*GadToolsBase;

	/* Blanker data. */

struct Task		*BlankTask;
struct Screen		*BlankScreen;

	/* Commodities interface data. */

struct MsgPort		*CxPort;
CxObj			*Broker;

	/* Gfx and gadtools data. */

struct Screen		*DefaultScreen;
APTR			 VisualInfo;
struct TextFont		*Topaz;
struct Gadget		*GadgetList;
struct Gadget		*GadgetArray[6];
struct Window		*Window;

	/* Rainbow colour table. */

UWORD			 Table[75];

	/* Key sequence buffers. */

UBYTE			 HotkeyBuffer[256],BlankScreenBuffer[256];

	/* Screen and pattern change timeout. */

ULONG			 ScreenCount = 0,PatternCount = 0,ScreenTimeout,PatternTimeout;

	/* The default font to be used by the control panel. */

struct TextAttr DefaultFont =
{
	(UBYTE *)"topaz.font",
	8,
	FS_NORMAL,
	FPF_ROMFONT
};

	/* A new broker definition, Commodities needs this. */

struct NewBroker NewBroker =
{
	NB_VERSION,
	"FracBlanker",
	"Fractal Blanker v1.3",
	"Screen Blanker",
	NBU_NOTIFY | NBU_UNIQUE,
	0,0,NULL,0
};

	/* Function prototypes. */

extern VOID __asm	Plot(register __a0 PLANEPTR Plane,register __d0 UWORD x,register __d1 UWORD y,register __d2 UWORD Modulo,register __d3 UWORD MaxX,register __d4 UWORD MaxY);
LONG __saveds		ShowTime(struct Gadget *SomeGadget,WORD Level);
struct Gadget *		CreateAllGadgets(struct Gadget **GadgetArray,struct Gadget **GadgetList,APTR VisualInfo,UWORD TopEdge);
VOID			ShutdownWindow(VOID);
BYTE			SetupWindow(VOID);
LONG __saveds __asm	SpriteBlanker(register __a1 struct BlankerInfo *BlankerInfo);
VOID			SpriteSwitch(BYTE Enabled);
VOID __saveds		BlankerAction(CxMsg *CxMessage,CxObj *CxObject);
VOID			ShutdownCx(VOID);
BYTE			SetupCx(UBYTE **ToolTypes);
VOID			HandleCxMsg(CxMsg *Message);
ULONG			Random(ULONG MaxValue);
VOID __saveds		Blanker(VOID);
VOID			CloseAll(LONG ReturnCode);
VOID			OpenAll(int argc,char **argv);
VOID __stdargs		main(int argc,char **argv);

	/* ShowTime(struct Gadget *SomeGadget,WORD Level):
	 *
	 *	Gadtools support routine, displays the timeouts.
	 */

LONG __saveds
ShowTime(struct Gadget *SomeGadget,WORD Level)
{
	STATIC UBYTE Buffer[30];

	if(Level)
		SPrintf(Buffer,"%2ld.%02ld",Level / 60,Level % 60);
	else
		SPrintf(Buffer,"-Off-");

	return((LONG)Buffer);
}

	/* CreateAllGadgets():
	 *
	 *	Gadtools support routine, creates all the gadgets
	 *	required by the control panel.
	 */

struct Gadget *
CreateAllGadgets(struct Gadget **GadgetArray,struct Gadget **GadgetList,APTR VisualInfo,UWORD TopEdge)
{
	struct Gadget		*Gadget;
	struct NewGadget	 NewGadget;
	UWORD			 Counter = 0;

	if(Gadget = CreateContext(GadgetList))
	{
		NewGadget . ng_Width		= 104;
		NewGadget . ng_Height		= 12;
		NewGadget . ng_GadgetText	= "_Screen Timeout        ";
		NewGadget . ng_TextAttr		= &DefaultFont;
		NewGadget . ng_VisualInfo	= VisualInfo;
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_LeftEdge		= (strlen(NewGadget . ng_GadgetText) + 2 - 1) * 8 + 2;
		NewGadget . ng_TopEdge		= 1 + TopEdge;

		GadgetArray[Counter++] = Gadget = CreateGadget(SLIDER_KIND,Gadget,&NewGadget,
			GT_Underscore,		'_',
			GTSL_Min,		0,
			GTSL_Max,		30 * 60,
			GTSL_Level,		ScreenTimeout,
			GTSL_DispFunc,		ShowTime,
			GTSL_LevelFormat,	"%s",
			GTSL_MaxLevelLen,	5,
		TAG_DONE);

		NewGadget . ng_GadgetText	= "_Pattern Change       ";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_TopEdge		= Gadget -> TopEdge + Gadget -> Height + 3;

		GadgetArray[Counter++] = Gadget = CreateGadget(SLIDER_KIND,Gadget,&NewGadget,
			GT_Underscore,		'_',
			GTSL_Min,		0,
			GTSL_Max,		30 * 60,
			GTSL_Level,		PatternTimeout,
			GTSL_DispFunc,		ShowTime,
			GTSL_LevelFormat,	"%s",
			GTSL_MaxLevelLen,	5,
		TAG_DONE);

		NewGadget . ng_GadgetText	= "Hot _Key";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Height		= 14;
		NewGadget . ng_TopEdge		= Gadget -> TopEdge + Gadget -> Height + 3;

		GadgetArray[Counter++] = Gadget = CreateGadget(STRING_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
			GTST_MaxChars,	256,
			GTST_String,	HotkeyBuffer,
		TAG_DONE);

		NewGadget . ng_GadgetText	= "_Blank Screen";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Height		= 14;
		NewGadget . ng_TopEdge		= Gadget -> TopEdge + Gadget -> Height + 4;

		GadgetArray[Counter++] = Gadget = CreateGadget(STRING_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
			GTST_MaxChars,	256,
			GTST_String,	BlankScreenBuffer,
		TAG_DONE);

		NewGadget . ng_GadgetText	= "_Hide";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_LeftEdge		= 10;
		NewGadget . ng_TopEdge		= HEIGHT - 3 - NewGadget . ng_Height;

		GadgetArray[Counter++] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
		TAG_DONE);

		NewGadget . ng_GadgetText	= "_Quit";
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_LeftEdge		= WIDTH - 10 - NewGadget . ng_Width;

		GadgetArray[Counter++] = Gadget = CreateGadget(BUTTON_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
		TAG_DONE);
	}

	return(Gadget);
}

	/* ShutdownWindow():
	 *
	 *	Closes the control panel.
	 */

VOID
ShutdownWindow()
{
	if(Window)
	{
		CloseWindow(Window);

		Window = NULL;
	}

	if(GadgetList)
	{
		FreeGadgets(GadgetList);

		GadgetList = NULL;
	}

	if(VisualInfo)
	{
		FreeVisualInfo(VisualInfo);

		VisualInfo = NULL;
	}

	if(DefaultScreen)
	{
		UnlockPubScreen(NULL,DefaultScreen);

		DefaultScreen = NULL;
	}

	if(Topaz)
	{
		CloseFont(Topaz);

		Topaz = NULL;
	}
}

	/* SetupWindow():
	 *
	 *	Creates the control panel and disables the screen
	 *	blanker.
	 */

BYTE
SetupWindow()
{
	if(BlankTask)
	{
		RemTask(BlankTask);

		BlankTask = NULL;
	}

	if(BlankScreen)
	{
		ScreenToBack(BlankScreen);

		CloseScreen(BlankScreen);

		BlankScreen = NULL;

		SpriteSwitch(TRUE);
	}

	if(Window)
		return(TRUE);

	if(Topaz = (struct TextFont *)OpenFont(&DefaultFont))
	{
		if(DefaultScreen = (struct Screen *)LockPubScreen(NULL))
		{
			if(VisualInfo = GetVisualInfo(DefaultScreen,TAG_DONE))
			{
				if(CreateAllGadgets(&GadgetArray[0],&GadgetList,VisualInfo,DefaultScreen -> WBorTop + DefaultScreen -> Font -> ta_YSize + 1))
				{
					if(Window = OpenWindowTags(NULL,
						WA_Width,	WIDTH,
						WA_Height,	HEIGHT + DefaultScreen -> Font -> ta_YSize - 8,

						WA_Activate,	TRUE,
						WA_DragBar,	TRUE,
						WA_DepthGadget,	TRUE,
						WA_CloseGadget,	TRUE,
						WA_RMBTrap,	TRUE,

						WA_IDCMP,	IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY | SLIDERIDCMP | BUTTONIDCMP,

						WA_Title,	"Fractal Blanker v1.3",
					TAG_DONE))
					{
						SetFont(Window -> RPort,Topaz);

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

						return(TRUE);
					}
				}
			}
		}
	}

	ShutdownWindow();

	return(FALSE);
}

	/* SpriteBlanker():
	 *
	 *	VBlank interrupt server, turns on/off all sprite channels
	 *	at the top of the frame.
	 */

LONG __saveds __asm
SpriteBlanker(register __a1 struct BlankerInfo *BlankerInfo)
{
		/* Are we still to run this piece of code? */

	if(BlankerInfo -> RingBack)
	{
			/* Enabled/disable the sprites. */

		if(BlankerInfo -> Enabled)
			ON_SPRITE
		else
		{
			UWORD i;

			OFF_SPRITE

			for(i = 0 ; i < 8 ; i++)
			{
				custom . spr[i] . dataa = 0;
				custom . spr[i] . datab = 0;
			}
		}

			/* Signal father task to remove the server. */

		Signal(BlankerInfo -> RingBack,1 << BlankerInfo -> Signal);

			/* Sprites are to be turned on only once. */

		if(BlankerInfo -> Enabled)
			BlankerInfo -> RingBack = NULL;
	}

	return(0);
}

	/* SpriteSwitch(BYTE Enabled):
	 *
	 *	Enable/disable sprite DMA by enabling a VBlank
	 *	interrupt server.
	 */

VOID
SpriteSwitch(BYTE Enabled)
{
	struct BlankerInfo __aligned	BlankerInfo;
	struct Interrupt __aligned	BlankInterrupt;

		/* Fill in the standard data. */

	BlankerInfo . RingBack	= SysBase -> ThisTask;
	BlankerInfo . Signal	= AllocSignal(-1);
	BlankerInfo . Enabled	= Enabled;

		/* Did we get the a signal? */

	if(BlankerInfo . Signal != -1)
	{
			/* Enable the interrupt server. */

		BlankInterrupt . is_Node . ln_Name	= "FracBlank.interrupt";
		BlankInterrupt . is_Node . ln_Type	= NT_INTERRUPT;
		BlankInterrupt . is_Node . ln_Pri	= 0;
		BlankInterrupt . is_Code		= (APTR)SpriteBlanker;
		BlankInterrupt . is_Data		= (APTR)&BlankerInfo;
		
		WaitTOF();
		WaitTOF();

		AddIntServer(INTB_VERTB,&BlankInterrupt);

			/* Wait for ringback signal. */

		Wait(1 << BlankerInfo . Signal);

		if(!Enabled)
		{
				/* Wait for another signal to make sure the
				 * sprites are really off.
				 */

			Wait(1 << BlankerInfo . Signal);
		}

			/* Remove the server. */

		RemIntServer(INTB_VERTB,&BlankInterrupt);

			/* Free the signal. */

		FreeSignal(BlankerInfo . Signal);
	}
}

	/* BlankerAction(CxMsg *CxMessage,CxObj *CxObject):
	 *
	 *	Commodities support routine, handles the Commodities
	 *	custom actions (in this case: filter the InputEvents
	 *	coming in and enable/disable the screen blanker).
	 */

VOID __saveds
BlankerAction(CxMsg *CxMessage,CxObj *CxObject)
{
	STATIC BYTE Count = 1;

	struct InputEvent *Event = (struct InputEvent *)CxMsgData(CxMessage);

		/* Push the blanker screen to the front if necessary. */

	if(BlankScreen)
	{
		if(BlankScreen -> TopEdge)
			MoveScreen(BlankScreen,0,-BlankScreen -> TopEdge);

		if(IntuitionBase -> FirstScreen != BlankScreen)
		{
			ScreenToFront(BlankScreen);

			SpriteSwitch(FALSE);
		}
	}

		/* This looks like a timer event. */

	if(Event -> ie_Class == IECLASS_TIMER && !Window)
	{
			/* Screen blanker still inactive? */

		if(!BlankTask)
		{
				/* Is there a timeout to take care of? */

			if(ScreenTimeout)
			{
					/* Are we ready to create the
					 * screenblanker?
					 */

				if(ScreenCount++ >= ScreenTimeout * 10)
				{
					if(BlankTask = (struct Task *)CreateTask("FracBlank.task",-20,Blanker,4000))
						PatternCount = 0;
				}
			}
		}
		else
		{
				/* Every 5/60 second we signal the blanker
				 * task to rotate the palette.
				 */

			if(Count++ == 2)
			{
				Signal(BlankTask,SIGBREAKF_CTRL_E);

				Count = 0;
			}

				/* Is it time to change the pattern? */

			if(PatternTimeout)
			{
				if(PatternCount++ >= PatternTimeout * 10)
				{
					Signal(BlankTask,SIGBREAKF_CTRL_D);

					PatternCount = 0;
				}
			}
		}
	}
	else
	{
			/* The following line determines whether
			 * the blanker is to be removed or to
			 * be left running.
			 */

		if((Event -> ie_Class == IECLASS_RAWKEY && !(Event -> ie_Code & IECODE_UP_PREFIX)) || Event -> ie_Class == IECLASS_RAWMOUSE)
		{
				/* Remove the blanker task. */

			if(BlankTask)
			{
				RemTask(BlankTask);

				BlankTask = NULL;
			}

				/* Close the blanker screen. */

			if(BlankScreen)
			{
				ScreenToBack(BlankScreen);

				SpriteSwitch(TRUE);

				CloseScreen(BlankScreen);

				BlankScreen = NULL;
			}
		}

		ScreenCount = 0;
	}
}

	/* ShutdownCx():
	 *
	 *	Close the Commodities interface.
	 */

VOID
ShutdownCx()
{
	if(CxPort)
	{
		struct Message *Message;

			/* Remove the broker. */

		if(Broker)
			DeleteCxObjAll(Broker);

			/* Remove the MsgPort from the public list. */

		RemPort(CxPort);

			/* Remove all pending messages. */

		while(Message = GetMsg(CxPort))
			ReplyMsg(Message);

			/* Delete the MsgPort. */

		DeleteMsgPort(CxPort);

		CxPort = NULL;
		Broker = NULL;
	}
}

	/* SetupCx(UBYTE **ToolTypes):
	 *
	 *	Set up the Commodities interface.
	 */

BYTE
SetupCx(UBYTE **ToolTypes)
{
		/* Cancel any previously made assignments. */

	ShutdownCx();

		/* Create a reply port. */

	if(CxPort = CreateMsgPort())
	{
			/* Fill in a unique name. */

		CxPort -> mp_Node . ln_Name = NewBroker . nb_Name;

			/* Add the reply port to the public list. */

		AddPort(CxPort);

			/* Set the Commodity priority if possible. */

		if(ToolTypes)
		{
			NewBroker . nb_Pri  = ArgInt(ToolTypes,"CX_PRIORITY",0);
			NewBroker . nb_Port = CxPort;
		}

			/* This Commodity features a control panel. */

		NewBroker . nb_Flags |= COF_SHOW_HIDE;

			/* Create the broker. */

		if(Broker = CxBroker(&NewBroker,NULL))
		{
			CxObj	*ObjectList;
			UBYTE	*String;

				/* Set the Commodity popup hotkey if possible. */

			if(ToolTypes)
			{
				String = ArgString(ToolTypes,"CX_POPKEY","shift f1");

				strcpy(HotkeyBuffer,String);
			}

				/* Link the hotkey. */

			AttachCxObj(Broker,HotKey(HotkeyBuffer,CxPort,POP_WINDOW));

				/* Determine the screen blanker hotkey. */

			if(ToolTypes)
			{
				String = ArgString(ToolTypes,"BLANKSCREEN","shift f2");

				strcpy(BlankScreenBuffer,String);
			}

				/* Link another hotkey. */

			AttachCxObj(Broker,HotKey(BlankScreenBuffer,CxPort,BLANK_SCREEN));

				/* Adjust the screen timeout if possible. */

			if(ToolTypes)
				ScreenTimeout = ArgInt(ToolTypes,"SCREENTIMEOUT",60);

				/* Adjust the pattern change timeout if possible. */

			if(ToolTypes)
				PatternTimeout = ArgInt(ToolTypes,"PATTERNTIMEOUT",60);

				/* Install the plain InputEvent handler. */

			ObjectList = CxCustom(BlankerAction,NULL);

				/* Any accumulated errors? */

			if(!CxObjError(ObjectList))
			{
					/* Add the custom object. */

				AttachCxObj(Broker,ObjectList);

					/* Any errors? */

				if(!CxObjError(Broker))
				{
						/* Activate the broker. */

					ActivateCxObj(Broker,TRUE);

					return(TRUE);
				}
			}
		}
	}

	ShutdownCx();

	return(FALSE);
}

	/* HandleCxMsg(CxMsg *Message):
	 *
	 *	Handle incoming Commodities messages.
	 */

VOID
HandleCxMsg(CxMsg *Message)
{
	ULONG MessageType = CxMsgID(Message),MessageID = CxMsgType(Message);

	ReplyMsg((struct Message *)Message);

		/* Take a look at the message type. */

	switch(MessageID)
	{
			/* It's a hotkey. */

		case CXM_IEVENT:	switch(MessageType)
					{
							/* Create the control panel. */

						case POP_WINDOW:	SetupWindow();
									break;

							/* Blank the screen. */

						case BLANK_SCREEN:	if(!BlankTask)
									{
											/* Blanker task isn't running yet,
											 * so let's create it.
											 */

										if(BlankTask = (struct Task *)CreateTask("FracBlank.task",-20,Blanker,4000))
											PatternCount = 0;
									}
									else
									{
											/* Tell the blanker task to change the pattern. */

										Signal(BlankTask,SIGBREAKF_CTRL_D);

										PatternCount = 0;
									}

									break;
					}

					break;

			/* It's an internal Commodities command. */

		case CXM_COMMAND:	switch(MessageType)
					{
							/* Disable the Commodity. */

						case CXCMD_DISABLE:	ActivateCxObj(Broker,FALSE);
									break;

							/* Enable the Commodity. */

						case CXCMD_ENABLE:	ActivateCxObj(Broker,TRUE);
									break;

							/* Create the control panel. */

						case CXCMD_APPEAR:
						case CXCMD_UNIQUE:	SetupWindow();
									break;

							/* Close the control panel. */

						case CXCMD_DISAPPEAR:	ShutdownWindow();
									break;

							/* Remove this Commodity. */

						case CXCMD_KILL:	CloseAll(RETURN_OK);
									break;
					}

					break;
	}
}

	/* Random(ULONG MaxValue):
	 *
	 *	Simple random number generation routine.
	 */

ULONG
Random(ULONG MaxValue)
{
	STATIC ULONG RandomSeed = 0xDEAD0123;

	RandomSeed = RandomSeed * custom . vhposr + 0xE153766F;

	return(RandomSeed % MaxValue);
}

	/* Blanker():
	 *
	 *	The screen blanker itself.
	 */

VOID __saveds
Blanker()
{
		/* Open the screen. */

	if(BlankScreen = OpenScreenTags(NULL,
		SA_Behind,	TRUE,
		SA_Quiet,	TRUE,
		SA_DisplayID,	HIRESLACE_KEY,
		SA_Overscan,	OSCAN_STANDARD,
		SA_Depth,	1,
	TAG_DONE))
	{
		STATIC BYTE	Wheel = 0;

		UWORD		Modulo = BlankScreen -> RastPort . BitMap -> BytesPerRow,Colours[2],OffsetX = BlankScreen -> Width >> 1,OffsetY = BlankScreen -> Height >> 1;
		PLANEPTR	Plane = BlankScreen -> RastPort . BitMap -> Planes[0];
		float		x = 0,y = 0,yy,a,b,c,sx,sy,mag,save;

			/* Provide starting numbers for the fractal
			 * parameters.
			 */

		a = (float)Random(300) / 100;
		b = (float)Random(100) / 100;
		c = (float)Random( 50) / 100;

		mag = (float)(1 << (Random(4) + 5)) * deg45;

			/* Set up the screen colour table. */

		Colours[0] = 0;
		Colours[1] = Table[Wheel];

		LoadRGB4(&BlankScreen -> ViewPort,Colours,2);

			/* Push the screen to the front. */

		ScreenToFront(BlankScreen);

			/* Turn off all sprite channels. */

		SpriteSwitch(FALSE);

			/* Go into fractal generation loop. */

		FOREVER
		{
				/* The original formula looks like
				 * this:
				 *                                     ½
				 *    x <- y - SIGN(x) * ABS(b * x - c)
				 *    y <- a - x
				 *
				 * I have split the calculation into
				 * several steps to save time and
				 * variables.
				 */

			yy = a - x;

			if((save = b * x - c) < 0)
				save = -save;

			if(x < 0)
				x = y + sqrt(save);
			else
				x = y - sqrt(save);

			y = yy;

				/* The resulting image appears to have
				 * been rotated by 45°, so we'll
				 * rotate the pixel coordinates by -45°
				 *
				 *    x <-  x * cos alpha + y * sin alpha
				 *    y <- -x * sin alpha + y * cos alpha
				 *
				 * We also magnify the image (i.e. the
				 * distribution of pixels) in the following
				 * lines.
				 */

			sx = mag * ( x + y);
			sy = mag * (-x + y);

				/* If the pixel happens to reside within
				 * the boundaries of the screen, draw it.
				 */

			Plot(Plane,(LONG)(sx) + OffsetX,(LONG)(sy) + OffsetY,Modulo,BlankScreen -> Width,BlankScreen -> Height);

				/* ^D tells the blanker to change the pattern. */

			if(SetSignal(0,0) & SIGBREAKF_CTRL_D)
			{
				SetSignal(0,SIGBREAKF_CTRL_D);

				SetRast(&BlankScreen -> RastPort,0);

				x = y = 0;

				a = (float)Random(300) / 100;
				b = (float)Random(100) / 100;
				c = (float)Random( 50) / 100;

				mag = (float)(1 << (Random(4) + 5)) * deg45;
			}

				/* ^E tells the blanker to rotate the
				 * colours.
				 */

			if(SetSignal(0,0) & SIGBREAKF_CTRL_E)
			{
				SetSignal(0,SIGBREAKF_CTRL_E);

				Wheel = (Wheel + 1) % 75;

				Colours[1] = Table[Wheel];

				LoadRGB4(&BlankScreen -> ViewPort,Colours,2);
			}
		}
	}

		/* Quietly remove ourselves. */

	Forbid();

	BlankTask = NULL;

	RemTask(SysBase -> ThisTask);
}

	/* CloseAll(LONG ReturnCode):
	 *
	 *	Free all resources and exit the program.
	 */

VOID
CloseAll(LONG ReturnCode)
{
	if(CxBase && IconBase)
	{
		ShutdownCx();

		ArgArrayDone();
	}

	if(BlankTask)
		RemTask(BlankTask);

	if(BlankScreen)
	{
		SpriteSwitch(TRUE);

		ScreenToBack(BlankScreen);

		CloseScreen(BlankScreen);
	}

	ShutdownWindow();

	if(IconBase)
		CloseLibrary(IconBase);

	if(CxBase)
		CloseLibrary(CxBase);

	if(GadToolsBase)
		CloseLibrary(GadToolsBase);

	if(GfxBase)
		CloseLibrary(GfxBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

	exit(ReturnCode);
}

	/* OpenAll(int argc,char **argv):
	 *
	 *	Open all resources, initialize the colour table and
	 *	create the Commodities interface.
	 */

VOID
OpenAll(int argc,char **argv)
{
	UBYTE **ToolTypes,*String;
	SHORT i,c = 0,r = 15,g = 0,b = 0;

		/* Create a table of rainbow colours. */

	for(i = 0 ; i < 16 ; i++)
		Table[c++] = (r << 8) | ((g++) << 4) | b;

	g = 15;
	r--;

	for(i = 0 ; i < 15 ; i++)
		Table[c++] = ((r--) << 8) | (g << 4) | b;

	r = 0;
	g--;
	b++;

	for(i = 0 ; i < 15 ; i++)
		Table[c++] = (r << 8) | ((g--) << 4) | (b++);

	g = 0;
	b = 15;
	r++;

	for(i = 0 ; i < 15 ; i++)
		Table[c++] = ((r++) << 8) | (g << 4) | b;

	r = 15;
	b--;

	for(i = 0 ; i < 14 ; i++)
		Table[c++] = (r << 8) | (g << 4) | (b--);

		/* Open the libraries we need. */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",37)))
		CloseAll(RETURN_FAIL + 0);

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",37)))
		CloseAll(RETURN_FAIL + 1);

	if(!(GadToolsBase = OpenLibrary("gadtools.library",37)))
		CloseAll(RETURN_FAIL + 2);

	if(!(CxBase = OpenLibrary("commodities.library",37)))
		CloseAll(RETURN_FAIL + 3);

	if(!(IconBase = OpenLibrary("icon.library",37)))
		CloseAll(RETURN_FAIL + 4);

		/* Parse the startup arguments. */

	ToolTypes = ArgArrayInit(argc,argv);

		/* Provide default values. */

	strcpy(HotkeyBuffer,		"shift f1");
	strcpy(BlankScreenBuffer,	"shift f2");

	ScreenTimeout	= 60;
	PatternTimeout	= 60;

		/* Create the commodities interface. */

	if(!SetupCx(ToolTypes))
		CloseAll(RETURN_FAIL + 5);

		/* Pop up the control panel if necessary. */

	if(ToolTypes)
	{
		String = ArgString(ToolTypes,"CX_POPUP","no");

		if(!strcmpi(String,"yes") || !strcmpi(String,"on"))
			SetupWindow();
	}
}

	/* main(int argc,char **argv):
	 *
	 *	That's where all the trouble starts.
	 */

VOID __stdargs
main(int argc,char **argv)
{
	ULONG SignalSet;

		/* Open everything we need. */

	OpenAll(argc,argv);

		/* Go into loop waiting for messages. */

	FOREVER
	{
		SignalSet = (1 << CxPort -> mp_SigBit) | SIGBREAKF_CTRL_E;

			/* If the window is still open, wait for
			 * some news.
			 */

		if(Window)
			SignalSet |= (1 << Window -> UserPort -> mp_SigBit);

		SignalSet = Wait(SignalSet);

			/* There are messages pending at the
			 * Commodities reply port.
			 */

		if(SignalSet & (1 << CxPort -> mp_SigBit))
		{
			CxMsg *Message;

			while(Message = (CxMsg *)GetMsg(CxPort))
				HandleCxMsg(Message);
		}

			/* ^E tells the program to quit. */

		if(SignalSet & SIGBREAKF_CTRL_E)
			CloseAll(RETURN_OK);

			/* If the control panel is still open,
			 * check for new messages.
			 */

		if(Window)
		{
			if(SignalSet & (1 << Window -> UserPort -> mp_SigBit))
			{
				struct IntuiMessage	*Massage;
				struct Gadget		*Gadget;
				ULONG			 Class,Code;

				while(Massage = (struct IntuiMessage *)GT_GetIMsg(Window -> UserPort))
				{
					Class	= Massage -> Class;
					Code	= Massage -> Code;
					Gadget	= (struct Gadget *)Massage -> IAddress;

					GT_ReplyIMsg(Massage);

					switch(Class)
					{
							/* Close the window. */

						case IDCMP_CLOSEWINDOW:	ShutdownWindow();
									break;

							/* Set the slider values. */

						case IDCMP_MOUSEMOVE:	switch(Gadget -> GadgetID)
									{
										case GAD_SCREENTIMEOUT:	ScreenCount = 0;
													ScreenTimeout = Code;
													break;

										case GAD_PATTERNCHANGE:	PatternCount = 0;
													PatternTimeout = Code;
													break;
									}

									break;

							/* Handle the keyboard shortcuts. */

						case IDCMP_VANILLAKEY:	switch(toupper(Code))
									{
										case 'S':	ScreenCount = 0;

												if(Code == 's')
												{
													if(ScreenTimeout + 1 <= 30 * 60)
														ScreenTimeout++;
													else
														ScreenTimeout = ScreenTimeout + 1 - 30 * 60;
												}
												else
												{
													if(ScreenTimeout + 10 <= 30 * 60)
														ScreenTimeout += 10;
													else
														ScreenTimeout = ScreenTimeout + 10 - 30 * 60;
												}

												GT_SetGadgetAttrs(GadgetArray[GAD_SCREENTIMEOUT],Window,NULL,
													GTSL_Level,ScreenTimeout,
												TAG_DONE);

												break;

										case 'P':	PatternCount = 0;

												if(Code == 'p')
												{
													if(PatternTimeout + 1 <= 30 * 60)
														PatternTimeout++;
													else
														PatternTimeout = PatternTimeout + 1 - 30 * 60;
												}
												else
												{
													if(PatternTimeout + 10 <= 30 * 60)
														PatternTimeout += 10;
													else
														PatternTimeout = PatternTimeout + 10 - 30 * 60;
												}

												GT_SetGadgetAttrs(GadgetArray[GAD_PATTERNCHANGE],Window,NULL,
													GTSL_Level,PatternTimeout,
												TAG_DONE);

												break;

										case 'K':	ActivateGadget(GadgetArray[GAD_HOTKEY],Window,NULL);
												break;

										case 'B':	ActivateGadget(GadgetArray[GAD_BLANKSCREEN],Window,NULL);
												break;

										case 'H':	ShutdownWindow();
												break;

										case 'Q':	CloseAll(RETURN_OK);

										default:	break;
									}

									break;

							/* Handle the gadgets themselves. */

						case IDCMP_GADGETUP:	switch(Gadget -> GadgetID)
									{
										case GAD_HOTKEY:	strcpy(HotkeyBuffer,((struct StringInfo *)Gadget -> SpecialInfo) -> Buffer);

													if(!SetupCx(NULL))
														CloseAll(RETURN_FAIL + 5);

													break;

										case GAD_BLANKSCREEN:	strcpy(BlankScreenBuffer,((struct StringInfo *)Gadget -> SpecialInfo) -> Buffer);

													if(!SetupCx(NULL))
														CloseAll(RETURN_FAIL + 5);

													break;

										case GAD_HIDE:		ShutdownWindow();
													break;

										case GAD_QUIT:		CloseAll(RETURN_OK);
									}

									break;
					}

						/* Window has been closed, do not
						 * continue requesting messages
						 * from the UserPort.
						 */

					if(!Window)
						break;
				}
			}
		}
	}
}
