/*
**	FracBlank - AmigaDOS 2.04 commodities utility screenblanker
**
**	Copyright © 1991-1992 by Olaf `Olsen' Barthel
**		All Rights Reserved
**
**	Cosmic flame fractal code derived from xlock source code
**
**	Copyright © 1988-1991 by Patrick J. Naughton.
*/

	/* Include the specific math pragmas/header files here (is there
	 * any way to figure this out by taking a look at undocumented
	 * 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)

	/* Use a simple address trick instead of the predefined
	 * address in amiga.lib.
	 */

#ifndef custom
#define custom (*(struct Custom *)0xDFF000)
#endif	/* custom */

	/* A couple of handy gadget control macros. */

#define GT_CHECKED(G)	((((struct Gadget *)(G)) -> Flags & GFLG_SELECTED) ? TRUE : FALSE)
#define GT_STRING(G)	(((struct StringInfo *)(((struct Gadget *)(G)) -> SpecialInfo)) -> Buffer)

	/* Hotkey IDs. */

enum	{	POP_WINDOW,BLANK_SCREEN };

	/* Gadget IDs. */

enum	{	GAD_SCREENTIMEOUT,GAD_MOUSETIMEOUT,GAD_PATTERNCHANGE,GAD_HOTKEY,
		GAD_BLANKSCREEN,GAD_KEYBLANK,GAD_FRACTAL,GAD_COLOUR,GAD_HIDE,GAD_QUIT };

	/* Some useful signals. */

#define SIG_BREAK	SIGBREAKF_CTRL_C
#define SIG_CHANGE	SIGBREAKF_CTRL_D
#define SIG_CYCLE	SIGBREAKF_CTRL_E
#define SIG_START	SIGBREAKF_CTRL_F
#define SIG_REFRESH	SIGBREAKF_CTRL_E
#define SIG_WAKEUP	SIGBREAKF_CTRL_F
#define SIG_CX		(1 << CxPort -> mp_SigBit)
#define SIG_WINDOW	(Window ? (1 << Window -> UserPort -> mp_SigBit) : NULL)

	/* Private mouse blanker signals. */

#define SIG_NOTICE	SIGBREAKF_CTRL_D
#define SIG_ON		SIGBREAKF_CTRL_E
#define SIG_OFF		SIGBREAKF_CTRL_F

	/* Number of patches to install. */

#define NUM_PATCHES	(sizeof(PatchTable) / sizeof(struct PatchInfo))

	/* Cosmic flame fractal parameters. */

#define MAXFLAMELEVEL	20
#define MAXTOTALPOINTS	20000

	/* Fractal types. */

enum	{	FRACTAL_REAL_PLANE,FRACTAL_COSMIC_FLAME,FRACTAL_RANDOM };

	/* Colour modes. */

enum	{	COLOUR_CYCLE,COLOUR_STATIC,COLOUR_MONO };

	/* A system library patch definition. */

struct PatchInfo
{
	APTR		 NewRoutine;
	LONG		 Offset;
	ULONG		*Destination;
};

	/* A window identification node, also contains information
	 * on the currently selected pointer sprite.
	 */

struct WindowNode
{
	struct MinNode	 Node;

	struct Window	*Window;

	UWORD		*Pointer;
	BYTE		 Height,
			 Width;
	BYTE		 XOffset,
			 YOffset;
	BYTE		 Off;
};

	/* The offsets of the system library routines we will patch. */

extern ULONG __far	 LVOClearPointer,
			 LVOSetPointer,
			 LVOOpenWindow,
			 LVOOpenWindowTagList,
			 LVOCloseWindow;

	/* Program revision tag. */

STATIC const UBYTE VersTag[] = "\0$VER: FracBlank 2.2 (4.4.92)";

	/* Shared library identifiers. */

extern struct ExecBase	*SysBase;

struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
struct Library		*CxBase,
			*IconBase,
			*GadToolsBase,
			*UtilityBase;

	/* The main program. */

struct Process		*MainProcess;

	/* Blanker data. */

struct Task		*BlankTask;
struct Screen		*BlankScreen;

	/* BlankerControl data. */

struct Task		*BlankerControlTask;

	/* Mouse blanker data. */

struct Task		*MouseBlankerTask;

	/* Commodities interface data. */

struct MsgPort		*CxPort;
CxObj			*Broker;

	/* Gfx and gadtools data. */

struct Screen		*DefaultScreen;
APTR			 VisualInfo;
struct TextFont		*LocalFont;
struct Gadget		*GadgetList;
struct Gadget		*GadgetArray[GAD_QUIT + 1];
struct Window		*Window;

	/* Window zoom data. */

struct IBox		 ZoomData = { -1,-1,-1,-1 };

	/* Window dimensions. */

WORD			 WindowWidth	= 284,
			 WindowHeight	= 113;

	/* Rainbow colour table. */

UWORD			 Table[75];

	/* No colours at all. */

UWORD			 Black[32];

	/* Declarations for cosmic flame blanker code. */

float			 Flame[2][3][2];
WORD			 FlameLevel,
			 FlameAlternateForm,
			 FlameModulo;
UWORD			 FlameColourTable[32];
BYTE			 FlameWheel,
			 FlameColour;
PLANEPTR		 FlamePlane;
ULONG			 FlamePoints;

	/* Key sequence buffers. */

UBYTE			 HotkeyBuffer[256],
			 BlankScreenBuffer[256];

	/* Screen and pattern change timeout. */

ULONG			 ScreenCount	= 0,
			 PatternCount	= 0,
			 ScreenTimeout	= 60,
			 MouseTimeout	= 5,
			 PatternTimeout	= 60;

	/* A flag which tells us whether to blank the mouse pointer
	 * on a keypress or not.
	 */

BYTE			 KeyBlank	= TRUE;

	/* Some kind of a semaphore to tell the blank task to stop drawing. */

BYTE			 StopDrawing	= FALSE;

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

struct TextAttr		 DefaultFont;
UBYTE			 DefaultFontName[256];
UWORD			 DefaultFontWidth;

	/* The colours of a rainbow (well, with a bit of imagination, just
	 * 32 colours in this rainbow for now).
	 */

UWORD Rainbow[32] =
{
	0x0000,0x0F00,0x0F30,0x0F50,
	0x0F70,0x0F90,0x0FB0,0x0FD0,
	0x0FF0,0x0DF0,0x0BF0,0x09F0,
	0x07F0,0x05F0,0x03F0,0x00F0,
	0x00D1,0x00B3,0x0095,0x0077,
	0x0059,0x003B,0x001D,0x000F,
	0x010F,0x030F,0x050F,0x070F,
	0x090F,0x0B0F,0x0D0F,0x0F0F
};

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

struct NewBroker NewBroker =
{
	NB_VERSION,
	"FracBlanker",
	"Fractal Blanker v2.2",
	"Screen Blanker",
	NBU_NOTIFY | NBU_UNIQUE,
	COF_SHOW_HIDE,
	0,NULL,0
};

	/* The fractal mode names. */

STRPTR FractalModes[] =
{
	"Real Plane",
	"Cosmic Flame",
	"Random",
	NULL
};

	/* The current fractal type. */

UBYTE	FractalType = FRACTAL_RANDOM;

	/* The colour mode names. */

STRPTR ColourModes[] =
{
	"Cycling Colours",
	"Static Colours",
	"Monochrome",
	NULL
};

	/* The current colour mode. */

UBYTE	ColourMode = COLOUR_CYCLE;

	/* Patch data. */

extern VOID __stdargs		 StackOldSetPointer(struct Window *Window,UWORD *Pointer,WORD Height,WORD Width,WORD XOffset,WORD YOffset);
extern VOID 			 NewSetPointer(VOID);

APTR				 OldSetPointer;

VOID			(* __asm OldClearPointer)(register __a0 struct Window *,register __a6 struct IntuitionBase *);
struct Window *		(* __asm OldOpenWindowTagList)(register __a0 struct NewWindow *,register __a1 struct TagItem *,register __a6 struct IntuitionBase *);
struct Window *		(* __asm OldOpenWindow)(register __a0 struct NewWindow *,register __a6 struct IntuitionBase *);
VOID			(* __asm OldCloseWindow)(register __a0 struct Window *,register __a6 struct IntuitionBase *);

	/* Mouse blanker function prototypes. */

VOID __stdargs __saveds		 StackNewSetPointer(struct Window *Window,UWORD *Pointer,WORD Height,WORD Width,WORD XOffset,WORD YOffset);

VOID __asm __saveds		 NewClearPointer(register __a0 struct Window *Window);
struct Window * __asm __saveds	 NewOpenWindowTagList(register __a0 struct NewWindow *NewWindow,register __a1 struct TagItem *TagList);
struct Window * __asm __saveds	 NewOpenWindow(register __a0 struct NewWindow *NewWindow);
VOID __asm __saveds		 NewCloseWindow(register __a0 struct Window *Window);

VOID __saveds			 MouseBlanker(VOID);

VOID __regargs			 UpdateNode(struct WindowNode *Node,struct Window *Window);
struct WindowNode * __regargs	 NewNode(struct Window *Window);

VOID				 TurnOff(VOID);
VOID				 TurnOn(VOID);

	/* The window and mouse pointer access list. */

struct SignalSemaphore	 WindowSemaphore;
struct List		 WindowList;

	/* Miscellaneous mouse blanker counters and flags. */

ULONG			 KeyBlankUseCnt	= 0,
			 KeyBlankCount	= 0;
BYTE			 AllSpritesOff	= FALSE,
			 KeyBlankStop	= FALSE;

	/* The table of patches to install. */

struct PatchInfo PatchTable[] =
{
	NewClearPointer,	(LONG)&LVOClearPointer,		(ULONG *)&OldClearPointer,
	NewSetPointer,		(LONG)&LVOSetPointer,		(ULONG *)&OldSetPointer,
	NewOpenWindowTagList,	(LONG)&LVOOpenWindowTagList,	(ULONG *)&OldOpenWindowTagList,
	NewOpenWindow,		(LONG)&LVOOpenWindow,		(ULONG *)&OldOpenWindow,
	NewCloseWindow,		(LONG)&LVOCloseWindow,		(ULONG *)&OldCloseWindow
};

	/* An entirely transparent sprite. */

UWORD __chip BlankSprite[(2 + 1) * 2] =
{
	0x0000,0x0000,

	0x0000,0x0000,

	0x0000,0x0000
};

	/* Function prototypes. */

extern VOID __asm	MonoPlot(register __a0 PLANEPTR Plane,register __d0 WORD x,register __d1 WORD y,register __d2 UWORD Modulo,register __d3 WORD MaxX,register __d4 WORD MaxY);
extern VOID __asm	MultiPlot(register __a0 struct Screen *,register __d0 WORD X,register __d1 WORD Y,register __d2 WORD Colour);

extern __stdargs	SPrintf(STRPTR Mem,STRPTR Args,...);
extern LONG __asm	Atol(register __a0 STRPTR String);

VOID __saveds		BlankerControl(VOID);
ULONG __regargs		Random(ULONG MaxValue);
VOID			RealPlane(VOID);
VOID			CosmicFlame(VOID);
BYTE			RecurseMono(float x,float y,WORD Level);
BYTE			RecurseColour(float x,float y,WORD Level);
VOID __saveds		Blanker(VOID);
VOID __saveds __stdargs	BlankerAction(CxMsg *CxMessage,CxObj *CxObject);
VOID			ShutdownCx(VOID);
BYTE __regargs		SetupCx(STRPTR *ToolTypes);
VOID __regargs		HandleCxMsg(CxMsg *Message);
LONG __saveds __stdargs	ShowTime(struct Gadget *SomeGadget,WORD Level);
struct Gadget *		CreateAllGadgets(struct Gadget **GadgetArray,struct Gadget **GadgetList,APTR VisualInfo,UWORD TopEdge);
VOID			ShutdownWindow(VOID);
VOID __regargs		CentreWindow(struct Screen *Screen,WORD *LeftEdge,WORD *TopEdge);
BYTE			SetupWindow(VOID);
VOID __regargs		CloseAll(LONG ReturnCode);
VOID __regargs		OpenAll(int argc,char **argv);
VOID __stdargs		main(int argc,char **argv);

	/* MouseBlanker():
	 *
	 *	The mouse pointer blanker task, patches a few system library functions
	 *	and turns the mouse pointer on and off.
	 */

VOID __saveds
MouseBlanker()
{
	struct Wedge *WedgeTable;

		/* Create the wedge table, note: this memory will never be
		 * freed!
		 */

	if(WedgeTable = (struct Wedge *)AllocMem(sizeof(struct Wedge) * NUM_PATCHES,MEMF_PUBLIC | MEMF_CLEAR))
	{
		struct WindowNode	*Node,
					*Next;
		ULONG			 IntuiLock,
					 SignalSet;
		struct Screen		*Screen;
		struct Window		*Window;
		BYTE			 Terminated = FALSE;
		WORD			 i;

			/* Set up the window list data. */

		InitSemaphore(&WindowSemaphore);

		NewList(&WindowList);

			/* Put all windows into the list. */

		IntuiLock = LockIBase(NULL);

		Screen = IntuitionBase -> FirstScreen;

		while(Screen)
		{
			Window = Screen -> FirstWindow;

			while(Window)
			{
				NewNode(Window);

				Window = Window -> NextWindow;
			}

			Screen = Screen -> NextScreen;
		}

		Forbid();

		UnlockIBase(IntuiLock);

			/* Install the patches. */

		for(i = 0 ; i < NUM_PATCHES ; i++)
		{
			WedgeTable[i] . Command	= JMP_ABS;
			WedgeTable[i] . Address	= PatchTable[i] . NewRoutine;

			*PatchTable[i] . Destination = (ULONG)SetFunction((struct Library *)IntuitionBase,PatchTable[i] . Offset,(APTR)&WedgeTable[i]);
		}

		CacheClearU();

			/* Ring back. */

		Signal(MainProcess,SIG_WAKEUP);

		Permit();

			/* Loop until terminated. */

		while(!Terminated)
		{
				/* Wait for a signal. */

			SignalSet = Wait(SIG_BREAK | SIG_ON | SIG_OFF);

				/* Are we to quit? */

			if(SignalSet & SIG_BREAK)
				Terminated = TRUE;

				/* Are we to turn the mouse pointer sprite back on? */

			if(SignalSet & SIG_ON)
				TurnOn();

				/* Are we to turn all mouse pointer sprites off? */

			if(SignalSet & SIG_OFF)
				TurnOff();
		}

		Forbid();

			/* Clear the notification signal. */

		SetSignal(0,SIG_NOTICE);

			/* Do not continue adding new nodes to the list. */

		KeyBlankStop = TRUE;

		Permit();

			/* Turn all sprites back on. */

		TurnOn();

			/* Remove the patches. */

		Forbid();

		for(i = 0 ; i < NUM_PATCHES ; i++)
			WedgeTable[i] . Address	= (APTR)*PatchTable[i] . Destination;

		CacheClearU();

		Permit();

			/* Loop until all patched routines are clear. */

		while(KeyBlankUseCnt > 0)
			Wait(SIG_NOTICE);

			/* Remove all windows from the list. */

		Node = (struct WindowNode *)WindowList . lh_Head;

		while(Next = (struct WindowNode *)Node -> Node . mln_Succ)
		{
			Remove((struct Node *)Node);

			FreeVec(Node);

			Node = Next;
		}
	}

		/* We're done now. */

	Forbid();

	Signal(MainProcess,SIG_WAKEUP);
}

	/* UpdateNode(struct WindowNode *Node,struct Window *Window):
	 *
	 *	Update a window node with the new pointer data.
	 */

VOID __regargs
UpdateNode(struct WindowNode *Node,struct Window *Window)
{
	Node -> Pointer	= Window -> Pointer;
	Node -> Height	= Window -> PtrHeight;
	Node -> Width	= Window -> PtrWidth;
	Node -> XOffset	= Window -> XOffset;
	Node -> YOffset	= Window -> YOffset;
}

	/* NewNode(struct Window *Window):
	 *
	 *	Create a new window node and link it into the window list.
	 */

struct WindowNode * __regargs
NewNode(struct Window *Window)
{
	struct WindowNode *Node;

	if(Node = (struct WindowNode *)AllocVec(sizeof(struct WindowNode),MEMF_PUBLIC | MEMF_CLEAR))
	{
		Node -> Window = Window;

		UpdateNode(Node,Window);

		AddTail(&WindowList,(struct Node *)Node);
	}

	return(Node);
}

	/* FindWindow(struct Window *Window):
	 *
	 *	Find a window within the window list.
	 */

struct WindowNode * __regargs
FindWindow(struct Window *Window)
{
	struct WindowNode	*Node,
				*Next;

	Node = (struct WindowNode *)WindowList . lh_Head;

	while(Next = (struct WindowNode *)Node -> Node . mln_Succ)
	{
		if(Window == Node -> Window)
			return(Node);

		Node = Next;
	}

	return(NULL);
}

	/* TurnOff():
	 *
	 *	Turn all mouse pointer sprites in all windows off.
	 */

VOID
TurnOff()
{
		/* Gain access to the window semaphore. */

	ObtainSemaphore(&WindowSemaphore);

		/* Are all sprites still turned off? */

	if(!AllSpritesOff)
	{
		struct WindowNode	*Node,
					*Next;

			/* Walk through the window list... */

		Node = (struct WindowNode *)WindowList . lh_Head;

		while(Next = (struct WindowNode *)Node -> Node . mln_Succ)
		{
				/* Is this mouse pointer still active? */

			if(!Node -> Off)
			{
					/* Update the node entry. */

				UpdateNode(Node,Node -> Window);

					/* Change the pointer. */

				StackOldSetPointer(Node -> Window,BlankSprite,1,16,0,0);

				Node -> Off = TRUE;
			}

			Node = Next;
		}

		AllSpritesOff = TRUE;
	}

	ReleaseSemaphore(&WindowSemaphore);
}

	/* TurnOn():
	 *
	 *	Turn all mouse pointer sprites in all windows back on.
	 */

VOID
TurnOn()
{
	struct WindowNode	*Node,
				*Next;

	ObtainSemaphore(&WindowSemaphore);

	AllSpritesOff = FALSE;

	KeyBlankCount = 0;

	Node = (struct WindowNode *)WindowList . lh_Head;

	while(Next = (struct WindowNode *)Node -> Node . mln_Succ)
	{
		if(Node -> Off)
		{
			if(Node -> Pointer)
				StackOldSetPointer(Node -> Window,Node -> Pointer,Node -> Height,Node -> Width,Node -> XOffset,Node -> YOffset);
			else
				OldClearPointer(Node -> Window,IntuitionBase);

			Node -> Off = FALSE;
		}

		Node = Next;
	}

	ReleaseSemaphore(&WindowSemaphore);
}

	/* NewClearPointer(register __a0 struct Window *Window):
	 *
	 *	Patch: resets the mouse pointer of a window
	 *	back to its default shape.
	 */

VOID __asm __saveds
NewClearPointer(register __a0 struct Window *Window)
{
	if(KeyBlankStop)
		OldClearPointer(Window,IntuitionBase);
	else
	{
		struct WindowNode *Node;

		ObtainSemaphore(&WindowSemaphore);

		KeyBlankUseCnt++;

		if(Node = FindWindow(Window))
		{
			if(!AllSpritesOff)
			{
				OldClearPointer(Window,IntuitionBase);

				Node -> Off = FALSE;
			}
			else
				Node -> Off = TRUE;

			Node -> Pointer = NULL;
		}
		else
			OldClearPointer(Window,IntuitionBase);

		KeyBlankUseCnt--;

		Signal(MouseBlankerTask,SIG_NOTICE);

		ReleaseSemaphore(&WindowSemaphore);
	}
}

	/* StackNewSetPointer():
	 *
	 *	Patch: attach a new mouse pointer sprite to a window.
	 */

VOID __stdargs __saveds
StackNewSetPointer(struct Window *Window,UWORD *Pointer,WORD Height,WORD Width,WORD XOffset,WORD YOffset)
{
	if(KeyBlankStop)
		StackOldSetPointer(Window,Pointer,Height,Width,XOffset,YOffset);
	else
	{
		struct WindowNode *Node;

		ObtainSemaphore(&WindowSemaphore);

		KeyBlankUseCnt++;

		if(Node = FindWindow(Window))
		{
			if(!AllSpritesOff)
			{
				StackOldSetPointer(Window,Pointer,Height,Width,XOffset,YOffset);

				Node -> Off = FALSE;
			}
			else
				Node -> Off = TRUE;

			Node -> Pointer	= Pointer;
			Node -> Height	= Height;
			Node -> Width	= Width;
			Node -> XOffset	= XOffset;
			Node -> YOffset	= YOffset;
		}
		else
			StackOldSetPointer(Window,Pointer,Height,Width,XOffset,YOffset);

		KeyBlankUseCnt--;

		Signal(MouseBlankerTask,SIG_NOTICE);

		ReleaseSemaphore(&WindowSemaphore);
	}
}

	/* NewOpenWindowTagList():
	 *
	 *	Patch: open a new window.
	 */

struct Window * __asm __saveds
NewOpenWindowTagList(register __a0 struct NewWindow *NewWindow,register __a1 struct TagItem *TagList)
{
	struct NewWindow OtherWindow;

	if(NewWindow)
	{
		if((NewWindow -> Type & SCREENTYPE) == CUSTOMSCREEN && NewWindow -> Screen == BlankScreen)
		{
			CopyMem(NewWindow,&OtherWindow,sizeof(struct NewWindow));

			NewWindow = &OtherWindow;

			NewWindow -> Type		= (NewWindow -> Type & ~SCREENTYPE) | WBENCHSCREEN;
			NewWindow -> Screen		= NULL;

			Signal(MouseBlankerTask,	SIG_ON);
			Signal(BlankerControlTask,	SIG_BREAK);
		}
	}

	if(KeyBlankStop)
		return(OldOpenWindowTagList(NewWindow,TagList,IntuitionBase));
	else
	{
		struct Window	*Window;
		ULONG		 Signals;

		ObtainSemaphore(&WindowSemaphore);

		KeyBlankUseCnt++;

		if(Window = OldOpenWindowTagList(NewWindow,TagList,IntuitionBase))
		{
			NewNode(Window);

			Signals = SIG_NOTICE | SIG_ON;
		}
		else
			Signals = SIG_NOTICE;

		KeyBlankUseCnt--;

		Signal(MouseBlankerTask,Signals);

		ReleaseSemaphore(&WindowSemaphore);

		return(Window);
	}
}

	/* NewOpenWindow():
	 *
	 *	Patch: open a new window.
	 */

struct Window * __asm __saveds
NewOpenWindow(register __a0 struct NewWindow *NewWindow)
{
	struct NewWindow OtherWindow;

	if(NewWindow)
	{
		if((NewWindow -> Type & SCREENTYPE) == CUSTOMSCREEN && NewWindow -> Screen == BlankScreen)
		{
			CopyMem(NewWindow,&OtherWindow,sizeof(struct NewWindow));

			NewWindow = &OtherWindow;

			NewWindow -> Type		= (NewWindow -> Type & ~SCREENTYPE) | WBENCHSCREEN;
			NewWindow -> Screen		= NULL;

			Signal(MouseBlankerTask,	SIG_ON);
			Signal(BlankerControlTask,	SIG_BREAK);
		}
	}

	if(KeyBlankStop)
		return(OldOpenWindow(NewWindow,IntuitionBase));
	else
	{
		struct Window	*Window;
		ULONG		 Signals;

		ObtainSemaphore(&WindowSemaphore);

		KeyBlankUseCnt++;

		if(Window = OldOpenWindow(NewWindow,IntuitionBase))
		{
			NewNode(Window);

			Signals = SIG_NOTICE | SIG_ON;
		}
		else
			Signals = SIG_NOTICE;

		KeyBlankUseCnt--;

		Signal(MouseBlankerTask,Signals);

		ReleaseSemaphore(&WindowSemaphore);

		return(Window);
	}
}

	/* NewCloseWindow():
	 *
	 *	Patch: close a window.
	 */

VOID __asm __saveds
NewCloseWindow(register __a0 struct Window *Window)
{
	struct WindowNode *Node;

	ObtainSemaphore(&WindowSemaphore);

	KeyBlankUseCnt++;

	if(Node = FindWindow(Window))
	{
		Remove((struct Node *)Node);

		FreeVec(Node);
	}

	OldCloseWindow(Window,IntuitionBase);

	KeyBlankUseCnt--;

	Signal(MouseBlankerTask,SIG_NOTICE);

	ReleaseSemaphore(&WindowSemaphore);
}

	/* BlankerControl():
	 *
	 *	The screen blanker control task.
	 */

VOID __saveds
BlankerControl()
{
	ULONG SignalSet;

	Forbid();

	FOREVER
	{
		SignalSet = Wait(SIG_BREAK | SIG_START | SIG_CHANGE | SIG_REFRESH);

			/* Remove both the screen and the blanker task? */

		if(SignalSet & SIG_BREAK)
		{
			if(BlankScreen)
				ScreenToBack(BlankScreen);

				/* Remove the blanker task. */

			if(BlankTask)
			{
				struct Task *Task = BlankTask;

					/* Remember the old address and clear it. */

				BlankTask = NULL;

					/* Make sure that the break signal is
					 * not set when we are waiting for a
					 * handshake.
					 */

				SetSignal(0,SIG_BREAK);

					/* Tell the blanker task to shut down. */

				Signal(Task,SIG_BREAK);

					/* Move the blanker task priority up. */

				SetTaskPri(Task,80);

					/* Wait for handshake signal... */

				Wait(SIG_BREAK);
			}

				/* Close the blanker screen. */

			if(BlankScreen)
			{
				struct Screen *Screen = BlankScreen;

				BlankScreen = NULL;

#ifdef USE_COP_LIST
				FreeVPortCopLists(&Screen -> ViewPort);

				RethinkDisplay();
#endif	/* USE_COP_LIST */
				CloseScreen(Screen);
			}

			continue;
		}

			/* Start the screen blanker? */

		if(SignalSet & SIG_START)
		{
			if(!BlankScreen)
			{
				struct Screen	*Screen,
						*FirstScreen;
				ULONG		 DisplayID,
						 Mode,
						 IntuiLock;

					/* Gain access to IntuitionBase. */

				IntuiLock = LockIBase(NULL);

					/* Determine the first screen. */

				FirstScreen = IntuitionBase -> FirstScreen;

					/* Inquire the display mode the first
					 * screen is in.
					 */

				DisplayID = GetVPModeID(&FirstScreen -> ViewPort);

					/* Surrender the lock. */

				UnlockIBase(IntuiLock);

					/* Set the approriate display mode. */

				if(ColourMode == COLOUR_MONO)
					Mode = HIRESLACE_KEY;
				else
					Mode = EXTRAHALFBRITELACE_KEY;

					/* This feature has been added for users
					 * of old Multiscan monitors which take
					 * ages to synchronize with different
					 * display modes. The programm will try
					 * to open the blanker screen in the same
					 * display mode as the first screen is in.
					 * Note that this trick will fail to work
					 * in ECS screen modes (productivity,
					 * superhires, etc.).
					 */

				switch(DisplayID & MONITOR_ID_MASK)
				{
					case NTSC_MONITOR_ID:	Mode |= NTSC_MONITOR_ID;
								break;

					case PAL_MONITOR_ID:	Mode |= PAL_MONITOR_ID;
								break;

					default:		Mode |= DEFAULT_MONITOR_ID;
								break;
				}

				if(Screen = OpenScreenTags(NULL,
					SA_Behind,	TRUE,
					SA_Quiet,	TRUE,
					SA_DisplayID,	Mode,
					SA_Overscan,	OSCAN_MAX,
					SA_Depth,	ColourMode == COLOUR_MONO ? 1 : 6,
				TAG_DONE))
				{
#ifdef USE_COP_LIST
					struct UCopList	*UserCopperList;

						/* I had a lot of problems trying to shut down
						 * the sprite display hardware completely.
						 * The mouse pointer would pop up again after
						 * a certain time interval, no matter what I
						 * did.
						 *
						 * Now, Amy, you had your chance, hadn't you?
						 *
						 * This is the big one: a user copper list will
						 * shut down the sprite hardware. Full stop.
						 */

					if(UserCopperList = (struct UCopList *)AllocMem(sizeof(struct UCopList),MEMF_PUBLIC|MEMF_CLEAR))
					{
							/* Set up for 3 instructions. */

						if(UCopperListInit(UserCopperList,3))
						{
								/* Wait for first line. */

							CWAIT(UserCopperList,0,0);

								/* Disable sprite DMA. */

							CMOVE(UserCopperList,custom . dmacon,BITCLR|DMAF_SPRITE);

								/* Terminate copper instruction list. */

							CEND(UserCopperList);

								/* Install the copper list. */

							Screen -> ViewPort . UCopIns = UserCopperList;

								/* Link it into the screen copper list. */

							RethinkDisplay();

								/* Set the colours to black. */
#endif	/* USE_COP_LIST */
							LoadRGB4(&Screen -> ViewPort,Black,32);

								/* Display the screen. */

							ScreenToFront(Screen);

							BlankScreen = Screen;
#ifdef USE_COP_LIST
						}
						else
						{
							CloseScreen(Screen);

							FreeMem(UserCopperList,sizeof(struct UCopList));
						}
					}
					else
						CloseScreen(Screen);
#endif	/* USE_COP_LIST */
				}
			}

			if(BlankScreen)
			{
				PatternCount = 0;

				if(!BlankTask)
					BlankTask = (struct Task *)CreateTask("FracBlank Blanker Task",-20,Blanker,1024 * MAXFLAMELEVEL);
			}

			continue;
		}

			/* Bring the screen to the front? */

		if(SignalSet & SIG_REFRESH)
		{
			if(BlankScreen)
			{
					/* Push the blanker screen to the front if necessary. */

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

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

			/* Change the patterns? */

		if(SignalSet & SIG_CHANGE)
		{
				/* Stop drawing patterns. */

			StopDrawing = TRUE;

			if(BlankScreen && BlankTask)
			{
					/* Clear the screen. */

				LoadRGB4(&BlankScreen -> ViewPort,Black,32);

					/* Then tell the blanker to restart. */

				Signal(BlankTask,SIG_CHANGE);
			}
		}
	}
}

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

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

	RandomSeed = RandomSeed * custom . vhposr + 0xE153766F;

	return(RandomSeed % MaxValue);
}

	/* RealPlane():
	 *
	 *	Draw real plane fractals.
	 */

VOID
RealPlane()
{
	STATIC BYTE Wheel = 0;

	UWORD	Colours[32],OffsetX = BlankScreen -> Width >> 1,OffsetY = BlankScreen -> Height >> 1;
	float	x = 0,y = 0,yy,a,b,c,sx,sy,mag;

		/* Are we running in monochrome mode? */

	if(ColourMode == COLOUR_MONO)
	{
		UWORD		Modulo	= BlankScreen -> RastPort . BitMap -> BytesPerRow;
		PLANEPTR	Plane	= BlankScreen -> RastPort . BitMap -> Planes[0];

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

		a = (float)(Random(700) + 5) / 100;
		b = (float)(Random(190) + 5) / 100;
		c = (float)(Random( 90) + 5) / 100;

		mag = (float)(1 << (Random(6) + 2)) * deg45;

			/* Set up the screen colour table. */

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

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

			/* Go into fractal generation loop. */

		FOREVER
		{
				/* Are we to shut down? */

			if(SetSignal(0,0) & SIG_BREAK)
			{
				Forbid();

				Signal(BlankerControlTask,SIG_BREAK);

				RemTask(SysBase -> ThisTask);
			}

				/* 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(x < 0)
				x = y + sqrt(fabs(b * x - c));
			else
				x = y - sqrt(fabs(b * x - c));

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

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

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

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

				Colours[1] = Table[Wheel];

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

				Wheel = (Wheel + 1) % 75;
			}

				/* Change the pattern? */

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

				SetRast(&BlankScreen -> RastPort,0);

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

				StopDrawing = FALSE;

				x = y = 0;

				a = (float)(Random(700) + 5) / 100;
				b = (float)(Random(190) + 5) / 100;
				c = (float)(Random( 90) + 5) / 100;

				mag = (float)(1 << (Random(6) + 2)) * deg45;
			}
		}
	}
	else
	{
		struct RastPort	*RPort;
		UWORD		 Count = 0;
		WORD		 i,NewBit = 0;
		BYTE		 Colour = 1,Plus = 1;

		a = (float)(Random(700) + 5) / 100;
		b = (float)(Random(190) + 5) / 100;
		c = (float)(Random( 90) + 5) / 100;

		mag = (float)(1 << (Random(6) + 2)) * deg45;

		Colours[0] = 0x000;

		for(i = 1 ; i < 32 ; i++)
			Colours[i] = Table[(Wheel + i) % 75];

		if(ColourMode == COLOUR_CYCLE)
			LoadRGB4(&BlankScreen -> ViewPort,Colours,32);
		else
			LoadRGB4(&BlankScreen -> ViewPort,Rainbow,32);

		Wheel = (Wheel + 1) % 75;

		RPort = &BlankScreen -> RastPort;

		FOREVER
		{
				/* Are we to shut down? */

			if(SetSignal(0,0) & SIG_BREAK)
			{
				Forbid();

				Signal(BlankerControlTask,SIG_BREAK);

				RemTask(SysBase -> ThisTask);
			}

			yy = a - x;

			if(x < 0)
				x = y + sqrt(fabs(b * x - c));
			else
				x = y - sqrt(fabs(b * x - c));

			y = yy;

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

			MultiPlot(BlankScreen,(WORD)(sx) + OffsetX,(WORD)(sy) + OffsetY,Colour | NewBit);

				/* Oh well, it's not that easy to
				 * produce decent colour values for
				 * the pixels to be rendered.
				 *
				 * The following statement will change
				 * the current drawing pen after exactly
				 * 1200 pixels have been rendered and will
				 * pick a new colour between 1 and 31.
				 */

			if(Count++ >= 1200)
			{
				Count = 0;

				if(NewBit)
				{
					NewBit = 0;

					Colour += Plus;

					if(!Colour)
					{
						Plus	= 1;
						Colour	= 2;
					}
					else
					{
						if(Colour == 32)
						{
							Plus	= -1;
							Colour	= 30;
						}
					}
				}
				else
					NewBit = 0x20;
			}

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

				for(i = 1 ; i < 32 ; i++)
					Colours[i] = Table[(Wheel + i) % 75];

				if(ColourMode == COLOUR_CYCLE)
					LoadRGB4(&BlankScreen -> ViewPort,Colours,32);

				Wheel = (Wheel + 1) % 75;
			}

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

				SetRast(RPort,0);

				StopDrawing = FALSE;

				if(ColourMode == COLOUR_CYCLE)
					LoadRGB4(&BlankScreen -> ViewPort,Colours,32);
				else
					LoadRGB4(&BlankScreen -> ViewPort,Rainbow,32);

				x = y = 0;

				a = (float)(Random(700) + 5) / 100;
				b = (float)(Random(190) + 5) / 100;
				c = (float)(Random( 90) + 5) / 100;

				mag = (float)(1 << (Random(6) + 2)) * deg45;
			}
		}
	}
}

	/* RecurseMono(float x,float y,WORD Level):
	 *
	 *	Cosmic flame calculation routine (monochrome).
	 */

BYTE
RecurseMono(float x,float y,WORD Level)
{
		/* Are we to shut down? */

	if(SetSignal(0,0) & SIG_BREAK)
	{
		Forbid();

		Signal(BlankerControlTask,SIG_BREAK);

		RemTask(SysBase -> ThisTask);
	}

	if(StopDrawing)
	{
		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

		/* Change the pattern? */

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

		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

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

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

		FlameColourTable[1] = Table[FlameWheel];

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

		FlameWheel = (FlameWheel + 1) % 75;
	}

	if(Level >= MAXFLAMELEVEL)
	{
		if((FlamePoints++) > MAXTOTALPOINTS)
			return(FALSE);
		else
			MonoPlot(FlamePlane,(WORD)((BlankScreen -> Width >> 1) * (x + 1.0)),(WORD)((BlankScreen -> Height >> 1) * (y + 1.0)),FlameModulo,BlankScreen -> Width,BlankScreen -> Height);
	}
	else
	{
		float	nx,ny;
		WORD	i;

		for(i = 0 ; i < 2 ; i++)
		{
			nx = Flame[0][0][i] * x + Flame[0][1][i] * y + Flame[0][2][i];
			ny = Flame[1][0][i] * x + Flame[1][1][i] * y + Flame[1][2][i];

			if(i < FlameAlternateForm)
			{
				nx = sin(nx);
				ny = sin(ny);
			}

			if(!StopDrawing)
			{
				if(!RecurseMono(nx,ny,Level + 1))
					return(FALSE);
			}
			else
				return(FALSE);
		}
	}

	return(TRUE);
}

	/* RecurseColour(float x,float y,WORD Level):
	 *
	 *	Cosmic flame calculation routine (colour).
	 */

BYTE
RecurseColour(float x,float y,WORD Level)
{
		/* Are we to shut down? */

	if(SetSignal(0,0) & SIG_BREAK)
	{
		Forbid();

		Signal(BlankerControlTask,SIG_BREAK);

		RemTask(SysBase -> ThisTask);
	}

	if(StopDrawing)
	{
		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

		/* Change the pattern? */

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

		FlameLevel	= 0;
		StopDrawing	= FALSE;

		return(FALSE);
	}

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

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

		if(ColourMode == COLOUR_CYCLE)
		{
			WORD i;

			for(i = 1 ; i < 32 ; i++)
				FlameColourTable[i] = Table[(FlameWheel + i) % 75];

			LoadRGB4(&BlankScreen -> ViewPort,FlameColourTable,32);

			FlameWheel = (FlameWheel + 1) % 75;
		}
	}

	if(Level >= MAXFLAMELEVEL)
	{
		if((FlamePoints++) > MAXTOTALPOINTS)
			return(FALSE);
		else
			MultiPlot(BlankScreen,(WORD)((BlankScreen -> Width >> 1) * (x + 1.0)),(WORD)((BlankScreen -> Height >> 1) * (y + 1.0)),FlameColour + 1);
	}
	else
	{
		float	nx,ny;
		WORD	i;

		for(i = 0 ; i < 2 ; i++)
		{
			nx = Flame[0][0][i] * x + Flame[0][1][i] * y + Flame[0][2][i];
			ny = Flame[1][0][i] * x + Flame[1][1][i] * y + Flame[1][2][i];

			if(i < FlameAlternateForm)
			{
				nx = sin(nx);
				ny = sin(ny);
			}

			if(!StopDrawing)
			{
				if(!RecurseColour(nx,ny,Level + 1))
					return(FALSE);
			}
			else
				return(FALSE);
		}
	}

	return(TRUE);
}

	/* CosmicFlame():
	 *
	 *	The cosmic flame screen blanker.
	 *
	 *	xlock.c - X11 client to lock a display and show a screen saver.
	 *
	 *	Copyright (c) 1988-91 by Patrick J. Naughton.
	 *
	 *	Permission to use, copy, modify, and distribute this software and its
	 *	documentation for any purpose and without fee is hereby granted,
	 *	provided that the above copyright notice appear in all copies and that
	 *	both that copyright notice and this permission notice appear in
	 *	supporting documentation.
	 */

VOID
CosmicFlame()
{
	WORD i,j,k;

		/* Monochrome mode? */

	if(ColourMode == COLOUR_MONO)
	{
		BYTE Alternate = FALSE;

			/* Initialize defaults. */

		FlameModulo	= BlankScreen -> RastPort . BitMap -> BytesPerRow;
		FlamePlane	= BlankScreen -> RastPort . BitMap -> Planes[0];

		FlameWheel	= 0;

			/* Set up the screen colour table. */

		FlameColourTable[0] = 0;
		FlameColourTable[1] = Table[FlameWheel];

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

		FlameLevel	= 0;

			/* Go into fractal generation loop. */

		FOREVER
		{
			if(!((FlameLevel++) % 20))
			{
				LoadRGB4(&BlankScreen -> ViewPort,Black,2);

				SetRast(&BlankScreen -> RastPort,0);

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

				Alternate = !Alternate;
			}

			if(Alternate)
				FlameAlternateForm = 0;
			else
				FlameAlternateForm = Random(2) + 2;

			for(k = 0 ; k < 2 ; k++)
			{
				for(i = 0 ; i < 2 ; i++)
				{
					for(j = 0; j < 3; j++)
						Flame[i][j][k] = ((float)Random(1024)) / 512.0 - 1.0;
				}
			}

			FlamePoints = 0;

			RecurseMono(0.0,0.0,0);
		}
	}
	else
	{
		BYTE Alternate = FALSE,Plus;

		FlameWheel = Random(75);

		FlameColourTable[0] = 0x000;

		for(i = 1 ; i < 32 ; i++)
			FlameColourTable[i] = Table[(FlameWheel + i) % 75];

		if(ColourMode == COLOUR_CYCLE)
			LoadRGB4(&BlankScreen -> ViewPort,FlameColourTable,32);
		else
			LoadRGB4(&BlankScreen -> ViewPort,Rainbow,32);

		FlameWheel	= (FlameWheel + 1) % 75;

		FlameColour	= Random(63);
		Plus		= 1;

		FlameLevel	= 0;

			/* Go into fractal generation loop. */

		FOREVER
		{
			if(!((FlameLevel++) % 20))
			{
				LoadRGB4(&BlankScreen -> ViewPort,Black,32);

				SetRast(&BlankScreen -> RastPort,0);

				if(ColourMode == COLOUR_CYCLE)
					LoadRGB4(&BlankScreen -> ViewPort,FlameColourTable,32);
				else
					LoadRGB4(&BlankScreen -> ViewPort,Rainbow,32);

				Alternate = !Alternate;
			}
			else
			{
				if(FlameColour == 62)
					Plus = -1;

				if(FlameColour == 0)
					Plus = 1;

				FlameColour += Plus;
			}

			if(Alternate)
				FlameAlternateForm = 0;
			else
				FlameAlternateForm = Random(2) + 2;

			for(k = 0 ; k < 2 ; k++)
			{
				for(i = 0 ; i < 2 ; i++)
				{
					for(j = 0; j < 3; j++)
						Flame[i][j][k] = ((float)Random(1024)) / 512.0 - 1.0;
				}
			}

			FlamePoints = 0;

			RecurseColour(0.0,0.0,0);
		}
	}
}

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

VOID __saveds
Blanker()
{
		/* Determine fractal type. */

	if(FractalType == FRACTAL_COSMIC_FLAME)
		CosmicFlame();
	else
	{
		if(FractalType == FRACTAL_REAL_PLANE)
			RealPlane();
		else
		{
			if(Random(42) >= 21)
				CosmicFlame();
			else
				RealPlane();
		}
	}

		/* Quietly remove ourselves. */

	Forbid();

	BlankTask = NULL;
}

	/* 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 __stdargs
BlankerAction(CxMsg *CxMessage,CxObj *CxObject)
{
	STATIC BYTE Count = 0;

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

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

	if(BlankScreen)
		Signal(BlankerControlTask,SIG_REFRESH);

		/* This looks like a timer event. */

	if(Event -> ie_Class == IECLASS_TIMER)
	{
		if(KeyBlankCount++ >= MouseTimeout * 10)
		{
			KeyBlankCount = 0;

			if(MouseTimeout)
				Signal(MouseBlankerTask,SIG_OFF);
		}

		if(!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)
						Signal(BlankerControlTask,SIG_START);
				}
			}
			else
			{
					/* Every 5/60 second we signal the blanker
					 * task to rotate the palette.
					 */

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

					Count = 0;
				}

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

				if(PatternTimeout)
				{
					if(PatternCount++ >= PatternTimeout * 10)
					{
						Signal(BlankerControlTask,SIG_CHANGE);

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

		switch(Event -> ie_Class)
		{
			case IECLASS_RAWKEY:	if(!(Event -> ie_Code & IECODE_UP_PREFIX) && !(Event -> ie_Qualifier & IEQUALIFIER_REPEAT))
						{
							if(KeyBlank)
								Signal(MouseBlankerTask,SIG_OFF);

							Signal(BlankerControlTask,SIG_BREAK);
						}

						break;

			case IECLASS_NEWPOINTERPOS:
			case IECLASS_POINTERPOS:
			case IECLASS_RAWMOUSE:	Signal(MouseBlankerTask,SIG_ON);

						Signal(BlankerControlTask,SIG_BREAK);

						break;

			default:		break;
		}

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

	/* GetSeconds(STRPTR String):
	 *
	 *	Calculates the number of seconds corresponding to
	 *	expressions such as `11:25' or `10'.
	 */

WORD
GetSeconds(STRPTR String)
{
	WORD i,Seconds;

	for(i = strlen(String) - 1 ; i >= 0 ; i--)
	{
		if(String[i] == ':')
		{
			Seconds = Atol(&String[i + 1]);

			String[i] = 0;

			Seconds += 60 * Atol(String);

			if(Seconds > 30 * 60)
				Seconds = 30 * 60;

			return(Seconds);
		}
	}

	if((Seconds = Atol(String)) > 30 * 60)
		Seconds = 30 * 60;

	return(Seconds);
}

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

BYTE __regargs
SetupCx(STRPTR *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);

			/* Install the replyport. */

		NewBroker . nb_Port = CxPort;

			/* Set the Commodity priority if possible. */

		if(ToolTypes)
			NewBroker . nb_Pri  = ArgInt(ToolTypes,"CX_PRIORITY",0);

			/* Create the broker. */

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

			if(ToolTypes)
			{
					/* Set the Commodity popup hotkey if possible. */

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

				strcpy(HotkeyBuffer,String);

					/* Determine the screen blanker hotkey. */

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

				strcpy(BlankScreenBuffer,String);

					/* Which fractal type? */

				String = ArgString(ToolTypes,"FRACTAL","RANDOM");

				if(!Stricmp(String,"REALPLANE") || !Stricmp(String,"REAL"))
					FractalType = FRACTAL_REAL_PLANE;
				else
				{
					if(!Stricmp(String,"COSMICFLAME") || !Stricmp(String,"FLAME"))
						FractalType = FRACTAL_COSMIC_FLAME;
					else
						FractalType = FRACTAL_RANDOM;
				}

					/* Which colour mode? */

				String = ArgString(ToolTypes,"COLOUR","CYCLE");

				if(!Stricmp(String,"STATIC"))
					ColourMode = COLOUR_STATIC;
				else
				{
					if(!Stricmp(String,"MONO"))
						ColourMode = COLOUR_MONO;
					else
						ColourMode = COLOUR_CYCLE;
				}

					/* Adjust the screen timeout if possible. */

				strcpy(Buffer,ArgString(ToolTypes,"SCREENTIMEOUT","1:00"));

				ScreenTimeout = GetSeconds(Buffer);

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

				strcpy(Buffer,ArgString(ToolTypes,"PATTERNTIMEOUT","1:00"));

				PatternTimeout = GetSeconds(Buffer);

					/* Adjust the mouse timeout if possible. */

				strcpy(Buffer,ArgString(ToolTypes,"MOUSETIMEOUT","0:05"));

				MouseTimeout = GetSeconds(Buffer);

					/* Determine whether to enable the keypress
					 * mouse blanker or not.
					 */

				String = ArgString(ToolTypes,"KEYBLANK","Yes");

				if(!Stricmp(String,"No") || !Stricmp(String,"Off"))
					KeyBlank = FALSE;
				else
					KeyBlank = TRUE;
			}

				/* Link the hotkey. */

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

				/* Link another hotkey. */

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

				/* 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 __regargs
HandleCxMsg(CxMsg *Message)
{
	ULONG MessageID = CxMsgID(Message),MessageType = CxMsgType(Message);

	ReplyMsg((struct Message *)Message);

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

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

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

						case POP_WINDOW:	SetupWindow();
									break;

							/* Blank the screen. */

						case BLANK_SCREEN:	if(!BlankTask)
										Signal(BlankerControlTask,SIG_START);
									else
									{
										Signal(BlankerControlTask,SIG_CHANGE);

										PatternCount = 0;
									}

									break;
					}

					break;

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

		case CXM_COMMAND:	switch(MessageID)
					{
							/* 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;
	}
}

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

LONG __saveds __stdargs
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;

	WindowWidth	= 42 * DefaultFontWidth + 20;
	WindowHeight	= TopEdge + 10 + 9 * (DefaultFont . ta_YSize + 6 + INTERHEIGHT) + 4;

	if(Gadget = CreateContext(GadgetList))
	{
		NewGadget . ng_Width		= DefaultFontWidth * 20;
		NewGadget . ng_Height		= DefaultFont . ta_YSize + 6;
		NewGadget . ng_GadgetText	= "_Screen Timeout      ";
		NewGadget . ng_TextAttr		= &DefaultFont;
		NewGadget . ng_VisualInfo	= VisualInfo;
		NewGadget . ng_GadgetID		= Counter;
		NewGadget . ng_Flags		= 0;
		NewGadget . ng_LeftEdge		= DefaultFontWidth * 22 + 10;
		NewGadget . ng_TopEdge		= TopEdge + 6;

		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	 = "_Mouse Timeout      ";
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

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

		NewGadget . ng_GadgetText	 = "_Pattern Change      ";
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

		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_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

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

		NewGadget . ng_GadgetText	 = "_Blank Screen";
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

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

		NewGadget . ng_GadgetText	 = "Ke_ypress blanks mouse";
		NewGadget . ng_Width		 = 26;
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

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

		NewGadget . ng_GadgetText	 = "_Fractal Type";
		NewGadget . ng_Width		 = DefaultFontWidth * 20;
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

		GadgetArray[Counter++] = Gadget = CreateGadget(CYCLE_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
			GTCY_Labels,	FractalModes,
			GTCY_Active,	FractalType,
		TAG_DONE);

		NewGadget . ng_GadgetText	 = "_Colour Mode";
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + INTERHEIGHT;

		GadgetArray[Counter++] = Gadget = CreateGadget(CYCLE_KIND,Gadget,&NewGadget,
			GT_Underscore,	'_',
			GTCY_Labels,	ColourModes,
			GTCY_Active,	ColourMode,
		TAG_DONE);

		NewGadget . ng_GadgetText	 = "_Hide";
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_Flags		 = 0;
		NewGadget . ng_LeftEdge		 = 10;
		NewGadget . ng_TopEdge		+= NewGadget . ng_Height + 4 + INTERHEIGHT;

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

		NewGadget . ng_GadgetText	 = "_Quit";
		NewGadget . ng_GadgetID		 = Counter;
		NewGadget . ng_LeftEdge		 = WindowWidth - 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);

		ZoomData . Left = -1;

		Window = NULL;
	}

	if(GadgetList)
	{
		FreeGadgets(GadgetList);

		GadgetList = NULL;
	}

	if(VisualInfo)
	{
		FreeVisualInfo(VisualInfo);

		VisualInfo = NULL;
	}

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

		DefaultScreen = NULL;
	}

	if(LocalFont)
	{
		CloseFont(LocalFont);

		LocalFont = NULL;
	}
}

	/* CentreWindow():
	 *
	 *	Adjust left/top coordinates of window to be opened to
	 *	become visible right below the mouse pointer.
	 */

VOID __regargs
CentreWindow(struct Screen *Screen,WORD *LeftEdge,WORD *TopEdge)
{
	(*LeftEdge)	= Screen -> MouseX - (WindowWidth >> 1);
	(*TopEdge)	= Screen -> MouseY - (WindowHeight >> 1);

	while((*LeftEdge) + WindowWidth > Screen -> Width)
		(*LeftEdge)--;

	while((*LeftEdge) < 0)
		(*LeftEdge)++;

	while((*TopEdge) + WindowHeight > Screen -> Height)
		(*TopEdge)--;

	while((*TopEdge) < 0)
		(*TopEdge)++;
}

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

BYTE
SetupWindow()
{
	if(BlankTask)
	{
		struct Task *Task = BlankTask;

		BlankTask = NULL;

		RemTask(Task);
	}

	if(BlankScreen)
	{
		struct Screen *Screen = BlankScreen;

		BlankScreen = NULL;

		ScreenToBack(Screen);

		FreeVPortCopLists(&Screen -> ViewPort);

		RethinkDisplay();

		CloseScreen(Screen);
	}

	if(Window)
	{
		struct Window	*ActiveWindow;
		struct Screen	*FirstScreen;
		ULONG		 IntuiLock;
		BYTE		 ZipIt;

		IntuiLock = LockIBase(NULL);

		FirstScreen	= IntuitionBase -> FirstScreen;
		ActiveWindow	= IntuitionBase -> ActiveWindow;

		UnlockIBase(IntuiLock);

		if(Window -> WScreen -> TopEdge > 0)
		{
			MoveScreen(Window -> WScreen,0,-Window -> WScreen -> TopEdge);

			ZipIt = FALSE;
		}
		else
			ZipIt = TRUE;

		if(FirstScreen != Window -> WScreen)
		{
			ScreenToFront(Window -> WScreen);

			ZipIt = FALSE;
		}
		else
			ZipIt = TRUE;

		if(ActiveWindow != Window)
		{
			ActivateWindow(Window);

			ZipIt = FALSE;
		}
		else
			ZipIt = TRUE;

		if(Window -> Height != WindowHeight)
			ZipIt = TRUE;

		WindowToFront(Window);

		if(ZipIt)
			ZipWindow(Window);

		return(TRUE);
	}
	else
	{
		Forbid();

		strcpy(DefaultFontName,GfxBase -> DefaultFont -> tf_Message . mn_Node . ln_Name);

		DefaultFont . ta_Name	= DefaultFontName;
		DefaultFont . ta_YSize	= GfxBase -> DefaultFont -> tf_YSize;
		DefaultFont . ta_Style	= GfxBase -> DefaultFont -> tf_Style;
		DefaultFont . ta_Flags	= GfxBase -> DefaultFont -> tf_Flags & ~FPF_REMOVED;

		DefaultFontWidth = GfxBase -> DefaultFont -> tf_XSize;

		Permit();

		if(LocalFont = (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))
					{
						WORD Left,Top;

						CentreWindow(DefaultScreen,&Left,&Top);

						if(ZoomData . Left == -1)
						{
							ZoomData . Left		= 0;
							ZoomData . Top		= 0;
							ZoomData . Width	= WindowWidth >> 1;
							ZoomData . Height	= DefaultScreen -> WBorTop + DefaultScreen -> Font -> ta_YSize + 1;
						}

						if(Window = OpenWindowTags(NULL,
							WA_CustomScreen,	DefaultScreen,

							WA_Left,		Left,
							WA_Top,			Top,

							WA_Width,		WindowWidth,
							WA_Height,		WindowHeight,

							WA_Activate,		TRUE,
							WA_DragBar,		TRUE,
							WA_DepthGadget,		TRUE,
							WA_CloseGadget,		TRUE,
							WA_RMBTrap,		TRUE,
							WA_Zoom,		&ZoomData,
							WA_NoCareRefresh,	TRUE,

							WA_IDCMP,		IDCMP_CLOSEWINDOW | IDCMP_VANILLAKEY | IDCMP_REFRESHWINDOW | IDCMP_NEWSIZE | SLIDERIDCMP | BUTTONIDCMP,

							WA_Title,		"Fractal Blanker v2.2",
						TAG_DONE))
						{
							SetFont(Window -> RPort,LocalFont);

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

							if(Window -> WScreen -> TopEdge > 0)
								MoveScreen(Window -> WScreen,0,-Window -> WScreen -> TopEdge);

							ScreenToFront(Window -> WScreen);

							UnlockPubScreen(NULL,DefaultScreen);

							DefaultScreen = NULL;

							return(TRUE);
						}
					}
				}
			}
		}

		ShutdownWindow();

		return(FALSE);
	}
}

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

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

		ArgArrayDone();
	}

	if(MouseBlankerTask)
	{
		Signal(MouseBlankerTask,SIG_BREAK);

		Wait(SIG_WAKEUP);
	}

	if(BlankerControlTask)
		RemTask(BlankerControlTask);

	if(BlankTask)
		RemTask(BlankTask);

	if(BlankScreen)
	{
		ScreenToBack(BlankScreen);

		FreeVPortCopLists(&BlankScreen -> ViewPort);

		RethinkDisplay();

		CloseScreen(BlankScreen);

		BlankScreen = NULL;
	}

	ShutdownWindow();

	if(IconBase)
		CloseLibrary(IconBase);

	if(UtilityBase)
		CloseLibrary(UtilityBase);

	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 __regargs
OpenAll(int argc,char **argv)
{
	UBYTE	**ToolTypes,*String;
	WORD	i,c = 0,r = 15,g = 0,b = 0;

		/* Set up the pointer to our process structure. */

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

		/* 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(!(UtilityBase = OpenLibrary("utility.library",37)))
		CloseAll(RETURN_FAIL + 4);

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

	if(!(BlankerControlTask = (struct Task *)CreateTask("FracBlank Control Task",100,BlankerControl,8192)))
		CloseAll(RETURN_FAIL + 6);

	if(!(MouseBlankerTask = (struct Task *)CreateTask("FracBlank Mouse Blanker Task",100,MouseBlanker,8192)))
		CloseAll(RETURN_FAIL + 7);

		/* Wait for handshake signal. */

	Wait(SIG_WAKEUP);

		/* Is the blanker still running? */

	if(!MouseBlankerTask)
		CloseAll(RETURN_FAIL + 8);

		/* Parse the startup arguments. */

	ToolTypes = ArgArrayInit(argc,argv);

		/* Provide default values. */

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

		/* Create the commodities interface. */

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

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

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

		if(!Stricmp(String,"yes"))
			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
	{
			/* Wait for a signal... */

		SignalSet = Wait(SIG_CX | SIG_BREAK | SIG_WAKEUP | SIG_WINDOW);

			/* Check the commodities toolkit reply port. */

		if(SignalSet & SIG_CX)
		{
			CxMsg *Message;

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

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

		if(SignalSet & SIG_BREAK)
			CloseAll(RETURN_OK);

			/* ^F tells the program to open its control panel. */

		if(SignalSet & SIG_WAKEUP)
			SetupWindow();

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

		if(SignalSet & SIG_WINDOW)
		{
			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;

						/* Gadtools does not seem to handle
						 * it yet: refresh the window
						 * contents.
						 */

					case IDCMP_NEWSIZE:	GT_RefreshWindow(Window,NULL);
								break;

						/* Set the slider values. */

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

									case GAD_MOUSETIMEOUT:	MouseTimeout	= Code;
												break;

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

								break;

						/* Handle the keyboard shortcuts. */

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

											if(ScreenTimeout + 1 <= 30 * 60)
												ScreenTimeout++;

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

											break;

									case 'S':	ScreenCount = 0;

											if(ScreenTimeout > 0)
												ScreenTimeout--;

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

											break;

									case 'm':	if(MouseTimeout + 1 <= 30 * 60)
												MouseTimeout++;

											GT_SetGadgetAttrs(GadgetArray[GAD_MOUSETIMEOUT],Window,NULL,
												GTSL_Level,MouseTimeout,
											TAG_DONE);

											break;

									case 'M':	if(MouseTimeout > 0)
												MouseTimeout--;

											GT_SetGadgetAttrs(GadgetArray[GAD_MOUSETIMEOUT],Window,NULL,
												GTSL_Level,MouseTimeout,
											TAG_DONE);

											break;

									case 'p':	PatternCount = 0;

											if(PatternTimeout + 1 <= 30 * 60)
												PatternTimeout++;

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

											break;

									case 'P':	PatternCount = 0;

											if(PatternTimeout > 0)
												PatternTimeout--;

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

											break;

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

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

									case 'y':
									case 'Y':	KeyBlank ^= TRUE;

											GT_SetGadgetAttrs(GadgetArray[GAD_KEYBLANK],Window,NULL,
												GTCB_Checked,KeyBlank,
											TAG_DONE);

											break;

									case 'f':	FractalType = (FractalType + 1) % 3;

											GT_SetGadgetAttrs(GadgetArray[GAD_FRACTAL],Window,NULL,
												GTCY_Active,FractalType,
											TAG_DONE);

											break;

									case 'F':	if(FractalType)
												FractalType--;
											else
												FractalType = FRACTAL_RANDOM;

											GT_SetGadgetAttrs(GadgetArray[GAD_FRACTAL],Window,NULL,
												GTCY_Active,FractalType,
											TAG_DONE);

											break;

									case 'c':	ColourMode = (ColourMode + 1) % 3;

											GT_SetGadgetAttrs(GadgetArray[GAD_COLOUR],Window,NULL,
												GTCY_Active,ColourMode,
											TAG_DONE);

											break;

									case 'C':	if(ColourMode)
												ColourMode--;
											else
												ColourMode = COLOUR_MONO;

											GT_SetGadgetAttrs(GadgetArray[GAD_COLOUR],Window,NULL,
												GTCY_Active,ColourMode,
											TAG_DONE);

											break;

									case '\033':
									case 'h':
									case 'H':	ShutdownWindow();
											break;

									case 3:
									case 'q':
									case 'Q':	CloseAll(RETURN_OK);

									default:	break;
								}

								break;

						/* Handle the gadgets themselves. */

					case IDCMP_GADGETUP:	switch(Gadget -> GadgetID)
								{
									case GAD_HOTKEY:	strcpy(HotkeyBuffer,GT_STRING(Gadget));

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

												break;

									case GAD_BLANKSCREEN:	strcpy(BlankScreenBuffer,GT_STRING(Gadget));

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

												break;

									case GAD_KEYBLANK:	KeyBlank = GT_CHECKED(Gadget);
												break;

									case GAD_FRACTAL:	FractalType = Code;
												break;

									case GAD_COLOUR:	ColourMode = Code;
												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;
			}
		}
	}
}
