/* DClock-Handler.c *********************************************************
 *
 *	DClock-Handler.c ------	Dumb clock main handler routines,
 *				display clock data, handle
 *				DisplayBeep, etc.
 *
 *	Author ----------------	Olaf 'Olsen' Barthel, MXM
 *				Brabeckstrasse 35
 *				D-3000 Hannover 71
 *
 *				Federal Republic of Germany
 *
 *	This program truly is in the PUBLIC DOMAIN. Written on a cold
 *	and damp September evening, hoping the next morning would  be
 *	better.
 *
 *	Compiled using Aztec C 5.0a, CygnusEd Professional 2 & ARexx.
 *
 ***************************************************************************/

	/* Signal flag aliases. */

#define SIG_CLICK	SIGBREAKF_CTRL_C
#define SIG_CLOSE	SIGBREAKF_CTRL_D
#define SIG_TIMER	SIGBREAKF_CTRL_E
#define SIG_TOGGL	SIGBREAKF_CTRL_F
#define SIG_SHAKE	SIGBREAKF_CTRL_D
#define SIG_BENCH	(1 << BenchSig)
#define SIG_WINDO	(1 << Window -> UserPort -> mp_SigBit)
#define SIG_DISPL	(1 << DisplaySig)
#define SIG_SPEECH	(1 << SpeechSig)

	/* Prototypes. */

struct InputEvent *	EventHandler(struct InputEvent *Event);
VOID 			Click(VOID);
VOID			FlushSound(VOID);
UBYTE			InitSound(VOID);
UBYTE			InitHandler(VOID);
VOID			FlushHandler(VOID);
VOID			ModifiedCloseWBench(struct Screen *);
VOID			AudioBeep(VOID);
VOID			VideoBeep(struct Screen *Screen,BYTE Perform);
VOID			ModifiedDisplayBeep(struct Screen *Screen);
VOID			PrintIt(STRPTR TimeBuff);
VOID			Ring(LONG Tea);
VOID			ShowTime(UBYTE ReallyDoIt,UBYTE Force);
ULONG			MaxMemSize(ULONG MemType);
struct Screen *		FindTheBench(VOID);
VOID			ShutDown(LONG HandShake);
LONG			RangeRand(LONG);
struct Process *	CreateFuncProc(char *Name,LONG Priority,APTR InitCode,ULONG StackSize);
UBYTE			MaxFontWidth(VOID);
VOID			DeleteDummyRPort(VOID);
BYTE			CreateDummyRPort(VOID);
VOID			Format(VOID *,char *,...);
LONG			PlayChime(VOID);
VOID			StopChime(VOID);
LONG			FindChunk(ULONG ChunkName,BPTR FileHandle);
LONG			LoadChimeSound(char *Name);

extern VOID		NewDisplayBeep(VOID);
extern VOID		NewCloseWBench(VOID);

	/* The ARexx server routines. */

STRPTR			CheckDClockStatus(STRPTR);
VOID			RexxServer(VOID);

	/* Interrupt register saving functions. */

VOID			int_start(VOID);
VOID			int_end(VOID);

	/* System specific functions. */

VOID			setenv(char *,char *);
LONG			_main(VOID);

	/* The speech server. */

VOID			SpeechServer(VOID);

	/* The magic pragmas. */

#pragma regcall(EventHandler(a0))
#pragma regcall(ModifiedDisplayBeep(a0))

	/* Some global data. */

struct IntuitionBase	*IntuitionBase;
struct GfxBase		*GfxBase;
extern struct ExecBase	*SysBase;
struct Library		*DiskfontBase;
struct RexxHostBase	*RexxHostBase;
struct Window		*Window;
struct Process		*HandlerProcess;
struct Process		*RexxProcess;
struct MsgPort		*RexxTaskPort;
struct Process		*SpeechProcess;
struct DSeg		*DSeg;
BYTE			 NewKick = FALSE;
BYTE			 Printed = FALSE;
LONG			 BenchSig = -1;
LONG			 DisplaySig = -1;
LONG			 SpeechSig = -1;

	/* Our current version tag. */

const char *VersionTag = "$VER: DClock-Handler 1.27 (26 Jul 1990)\n\r";

	/* Online time. */

UBYTE OnlineHours = 0,OnlineMinutes = 0,OnlineSeconds = 0;

	/* The chime data. */

struct IOAudio		*ChimeAudioBlock;
struct MsgPort		*ChimeReplyPort;
UBYTE			*ChimeWaveMap;

	/* Our dummy RastPort. */

struct BitMap		*DummyMap;
struct RastPort		*DummyRPort;
struct TextFont		*DummyFont;
BYTE			 DummyDepth;

	/* Static DClock window size and location. */

LONG LeftEdge = 409,TopEdge = 1,Width = 176,Height = 8;

	/* Audio.device control structures. */

struct MsgPort		*AudioPort;
struct IOAudio		*Audio;

	/* Input.device control structures. */

struct MsgPort		*InputDevPort;
struct IOStdReq		*InputRequestBlock;

	/* The interrupt handler control. */

struct Interrupt	*InputHandler;

	/* Console.device control structures. */

struct Device		*ConsoleDevice;
struct IOStdReq		*ConStdReq;
struct InputEvent	*CopyEvent;

	/* The library offset pointers (old ones). */

VOID *OldDisplayBeep;
VOID *OldCloseWBench;

	/* External data. */

extern ULONG		SoundData[67];
extern ULONG		RingData[1831];
extern USHORT		ClockMap[456];
extern struct TextAttr	DefaultFont;
extern struct IntuiText	TimeString;
extern struct NewWindow	NewWindow;
extern UBYTE		AnyChannel[4];
extern UBYTE		SquareWave[4];
extern struct Image	ClockImage;
extern struct Gadget	ClockGadget[2];
extern struct NewWindow	NewClockWindow;
extern struct IntuiText	ClockTxt[4];

	/* EventHandler(Event) :
	 *
	 *	This is the main interface to the handler
	 *	routine.
	 */

struct InputEvent *
EventHandler(struct InputEvent *Event)
{
	int_start();

		/* User wants to toggle text and memory mode. */

	if(Event -> ie_Class == IECLASS_RAWKEY && Event -> ie_Code == 0x5F && Event -> ie_Qualifier == RIGHT_AMIGA)
	{
		Event -> ie_Class = IECLASS_NULL;

		if(DSeg -> Child)
			Signal(DSeg -> Child,SIG_TOGGL);
	}

		/* User wants to know the time? */

	if(Event -> ie_Class == IECLASS_RAWKEY && Event -> ie_Code == 0x5F && Event -> ie_Qualifier == RIGHT_ALT)
	{
		Event -> ie_Class = IECLASS_NULL;

		if(DSeg -> Child)
			Signal(DSeg -> Child,SIG_SPEECH);
	}

		/* Modify the time display size? */

	if(Event -> ie_Class == IECLASS_RAWKEY && Event -> ie_Code == 0x46 && Event -> ie_Qualifier == RIGHT_AMIGA)
	{
		Event -> ie_Class = IECLASS_NULL;

		if(DSeg -> Child)
			Signal(DSeg -> Child,SIG_DISPL);
	}

		/* If we can use it, initialize the copyevent and signal
		 * the main process to produce a click.
		 */

	if(Event -> ie_Class == IECLASS_RAWKEY && !(Event -> ie_Code & IECODE_UP_PREFIX) && DSeg -> Click)
	{
		CopyEvent -> ie_Class		= Event -> ie_Class;
		CopyEvent -> ie_Code		= Event -> ie_Code;
		CopyEvent -> ie_Qualifier	= Event -> ie_Qualifier;

			/* This is safe from interrupt code, or
			 * at least should be.
			 */

		if(DSeg -> Child)
			Signal(DSeg -> Child,SIG_CLICK);
	}

		/* I had a lot of trouble getting DClock-Handler to
		 * work with timer.device. Well, the only thing
		 * happening in time were system crashes, so I
		 * decided to use the timer entries of the
		 * InputEvent structures.
		 */

	if(Event -> ie_TimeStamp . tv_secs != DSeg -> LastSecs)
	{
		DSeg -> LastSecs = Event -> ie_TimeStamp . tv_secs;

		if(DSeg -> Child)
			Signal(DSeg -> Child,SIG_TIMER);

		/* Each second we take a look at CIA B, port A
		 * to find out if a carrier signal is currently
		 * present at the serial port. Note that the port
		 * bits are low-active.
		 */

		if(!(ciab . ciapra & CIAF_COMCD))
		{
				/* Are we online? */

			if(!DSeg -> Online)
			{
				/* Reset counters. */

				OnlineHours = OnlineMinutes = OnlineSeconds = 0;
				DSeg -> Online = TRUE;
			}

				/* Increment time counter. */

			if(++OnlineSeconds == 60)
			{
				OnlineSeconds = 0;

				if(++OnlineMinutes == 60)
				{
					OnlineMinutes = 0;

					++OnlineHours;
				}
			}
		}
		else
			DSeg -> Online = FALSE;
	}

	int_end();

	return(Event);
}

	/* Click() :
	 *
	 *	Produces the click (or what did you expect?).
	 */

VOID
Click()
{
	char	PrimaryBuffer[11];	/* Rawkey conversion buffer. */
	SHORT	i;

		/* Erase the buffer. */

	for(i = 0 ; i < 11 ; i++)
		PrimaryBuffer[i] = 0;

		/* Convert the input event according to the
		 * current keymap settings.
		 */

	RawKeyConvert(CopyEvent,PrimaryBuffer,10,NULL);

		/* If it didn't produce a sensible result,
		 * don't click.
		 */

	if(!PrimaryBuffer[0])
		return;

	Audio -> ioa_Volume = DSeg -> ClickVolume;

		/* Let it click. */

Tick:	if(CheckIO(Audio))
		BeginIO(Audio);
}

	/* FlushSound() :
	 *
	 *	Send the sound control data to NIL:
	 */

VOID
FlushSound()
{
	if(Audio)
	{
			/* Audio device still open? */

		if(Audio -> ioa_Request . io_Device)
		{
			if(!CheckIO(Audio))
				WaitIO(Audio);

				/* Free the channel(s). */

			CloseDevice(Audio);
		}

			/* Free the audio control block. */

		FreeMem(Audio,sizeof(struct IOAudio));
	}

		/* Delete the replyport. */

	if(AudioPort)
		DeletePort(AudioPort);
}

	/* InitSound() :
	 *
	 *	Sets up the audio control structures.
	 */

UBYTE
InitSound()
{
	if(!(Audio = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC | MEMF_CLEAR)))
		return(FALSE);

	if(!(AudioPort = (struct MsgPort *)CreatePort(NULL,0)))
		return(FALSE);

		/* Open the channel. */

	if(OpenDevice(AUDIONAME,0,Audio,0))
		return(FALSE);

		/* Try to allocate a vacant channel. */

	Audio -> ioa_Request . io_Command			= ADCMD_ALLOCATE;
	Audio -> ioa_Request . io_Flags				= ADIOF_NOWAIT;
	Audio -> ioa_Request . io_Message . mn_Node . ln_Pri	= 100;
	Audio -> ioa_Request . io_Message . mn_ReplyPort	= AudioPort;
	Audio -> ioa_Data					= AnyChannel;
	Audio -> ioa_Length					= 4;

		/* Try the allocation. */

	BeginIO(Audio);

		/* Did it return an error? */

	if(WaitIO(Audio))
		return(FALSE);

		/* Prepare it for the click. */

	Audio -> ioa_Request . io_Command	= CMD_WRITE;
	Audio -> ioa_Request . io_Flags		= ADIOF_PERVOL | ADIOF_NOWAIT;
	Audio -> ioa_Period			= 180;
	Audio -> ioa_Volume			= 0;
	Audio -> ioa_Length			= 270;
	Audio -> ioa_Data			= (UBYTE *)SoundData;
	Audio -> ioa_Cycles			= 1;

		/* Click once. */

	BeginIO(Audio);
	WaitIO(Audio);

	return(TRUE);
}

	/* InitHandler() :
	 *
	 *	Open the console.device for keymap translation
	 *	and add the input.device handler.
	 */

UBYTE
InitHandler()
{
	if(!(ConStdReq = (struct IOStdReq *)AllocMem(sizeof(struct IOStdReq),MEMF_PUBLIC | MEMF_CLEAR)))
		return(FALSE);

	if(OpenDevice("console.device",-1,ConStdReq,0))
		return(FALSE);

	if(!(InputDevPort = (struct MsgPort *)CreatePort(NULL,0)))
		return(FALSE);

	if(!(InputRequestBlock = (struct IOStdReq *)CreateStdIO(InputDevPort)))
		return(FALSE);

	if(OpenDevice("input.device",0,InputRequestBlock,0))
		return(FALSE);

	if(!(InputHandler = (struct Interrupt *)AllocMem(sizeof(struct Interrupt),MEMF_PUBLIC | MEMF_CLEAR)))
		return(FALSE);

	if(!(CopyEvent = (struct InputEvent *)AllocMem(sizeof(struct InputEvent),MEMF_PUBLIC | MEMF_CLEAR)))
		return(FALSE);

	InputHandler -> is_Node . ln_Name	= "DClock-Handler";
	InputHandler -> is_Node . ln_Pri	= 51;
	InputHandler -> is_Code			= (VOID *)EventHandler;

	InputRequestBlock -> io_Command		= IND_ADDHANDLER;
	InputRequestBlock -> io_Data		= (APTR)InputHandler;

	DoIO(InputRequestBlock);

	ConsoleDevice = ConStdReq -> io_Device;

	return(TRUE);
}

	/* FlushHandler() :
	 *
	 *	Closes the console.device and removes the
	 *	input.device handler from the chain.
	 */

VOID
FlushHandler()
{
	if(ConsoleDevice)
		CloseDevice(ConStdReq);

	if(InputRequestBlock)
	{
		if(InputRequestBlock -> io_Device)
		{
			InputRequestBlock -> io_Command	= IND_REMHANDLER;
			InputRequestBlock -> io_Data	= (APTR)InputHandler;

			DoIO(InputRequestBlock);

			CloseDevice(InputRequestBlock);
		}

		DeleteStdIO(InputRequestBlock);
	}

	if(ConStdReq)
		FreeMem(ConStdReq,sizeof(struct IOStdReq));

	if(CopyEvent)
		FreeMem(CopyEvent,sizeof(struct InputEvent));

	if(InputHandler)
		FreeMem(InputHandler,sizeof(struct Interrupt));

	if(InputDevPort)
		DeletePort(InputDevPort);
}

	/* ModifiedCloseWBench():
	 *
	 *	Tells DClock to close its window before the
	 *	Workbench screen gets closed.
	 */

VOID
ModifiedCloseWBench()
{
	if(Window)
	{
		struct Task	*ThisTask = SysBase -> ThisTask;
		BYTE		 CurrentPri;

			/* Shut down... */

		Signal(DSeg -> Child,SIG_BENCH);

			/* Careful - rather rude window close check,
			 * don't waste too much time in the loop.
			 */

		CurrentPri = SetTaskPri(ThisTask,0);

		while(Window);

		SetTaskPri(ThisTask,CurrentPri);
	}
}

VOID
AudioBeep()
{
	struct IOAudio	*AudioBlock;
	struct MsgPort	*ReplyPort;

		/* Allocate some driver memory. */

	if(AudioBlock = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC | MEMF_CLEAR))
	{
			/* Time for a replyport? */

		if(ReplyPort = (struct MsgPort *)CreatePort(NULL,0))
		{
			AudioBlock -> ioa_Request . io_Message . mn_ReplyPort = ReplyPort;

			if(!OpenDevice(AUDIONAME,0,AudioBlock,0))
			{
					/* Set up initial driver data. */

				AudioBlock -> ioa_Request . io_Command				= ADCMD_ALLOCATE;
				AudioBlock -> ioa_Request . io_Message . mn_Node . ln_Pri	= 90;
				AudioBlock -> ioa_Data						= &AnyChannel[0];
				AudioBlock -> ioa_Length					= 4;

				if(!DoIO(AudioBlock))
				{
					AudioBlock -> ioa_Request . io_Command	= CMD_WRITE;
					AudioBlock -> ioa_Request . io_Flags	= ADIOF_PERVOL;
					AudioBlock -> ioa_Period		= 447;
					AudioBlock -> ioa_Volume		= 64 / 2;
					AudioBlock -> ioa_Cycles		= 150;
					AudioBlock -> ioa_Data			= &SquareWave[0];
					AudioBlock -> ioa_Length		= 4;

						/* Beeep! */

					BeginIO(AudioBlock);
					WaitIO(AudioBlock);
				}

				CloseDevice(AudioBlock);
			}

			DeletePort(ReplyPort);
		}

		FreeMem(AudioBlock,sizeof(struct IOAudio));
	}
}

	/* VideoBeep(Screen,Perform):
	 *
	 *	Handles the visual part of the DisplayBeep,
	 *	flashes a particular screen or restores its
	 *	original colour (well, hope so).
	 */

VOID
VideoBeep(struct Screen *Screen,BYTE Perform)
{
	UBYTE R,G,B;

		/* Beep this screen? */

	if(Perform)
	{
			/* Is it already beeping? */

		if(!(Screen -> Flags & BEEPING))
		{
				/* This one's beeping. */

			Screen -> Flags |= BEEPING;

				/* Don't forget this one. */

			Screen -> SaveColor0 = GetRGB4(Screen -> ViewPort . ColorMap,0);

				/* Reverse the colour. */

			R = ((Screen -> SaveColor0 >> 8) & 0xF) ^ 0xF;
			G = ((Screen -> SaveColor0 >> 4) & 0xF) ^ 0xF;
			B = ((Screen -> SaveColor0     ) & 0xF) ^ 0xF;

				/* Set it. */

			SetRGB4(&Screen -> ViewPort,0,R,G,B);
		}
	}
	else
	{
			/* Is this one beeping? */

		if(Screen -> Flags & BEEPING)
		{
				/* This one isn't beeping any longer. */

			Screen -> Flags &= ~BEEPING;

				/* Restore the saved colour. */

			R = ((Screen -> SaveColor0 >> 8) & 0xF);
			G = ((Screen -> SaveColor0 >> 4) & 0xF);
			B = ((Screen -> SaveColor0     ) & 0xF);

			SetRGB4(&Screen -> ViewPort,0,R,G,B);
		}
	}
}

	/* ModifiedDisplayBeep(Screen):
	 *
	 *	Magic replacement for usual DisplayBeep()
	 *	function.
	 */

VOID
ModifiedDisplayBeep(struct Screen *Screen)
{
		/* Flash a particular screen. */

	if(Screen)
	{
		VideoBeep(Screen,TRUE);

		if(DSeg -> Beep)
			AudioBeep();

		VideoBeep(Screen,FALSE);
	}
	else
	{
			/* Flash all screens. */

		ULONG IntuiLock;

			/* Where's the first one? Has anybody
			 * used the LockIBase() function so
			 * far (save me)?
			 */

		IntuiLock = LockIBase(NULL);

		Screen = IntuitionBase -> FirstScreen;

			/* Walk through the screens flashing them all. */

		do
			VideoBeep(Screen,TRUE);
		while(Screen = Screen -> NextScreen);

		UnlockIBase(IntuiLock);

			/* Let it resound. */

		if(DSeg -> Beep)
			AudioBeep();

			/* Again: where's the first screen? */

		IntuiLock = LockIBase(NULL);

		Screen = IntuitionBase -> FirstScreen;

		do
			VideoBeep(Screen,FALSE);
		while(Screen = Screen -> NextScreen);

		UnlockIBase(IntuiLock);
	}
}

	/* PrintIt(TimeBuff,CharOffset):
	 *
	 *	Prints the formatted string into the Workbench title bar.
	 */

VOID
PrintIt(STRPTR TimeBuff)
{
	BYTE Length = strlen((char *)TimeBuff);
	BYTE Offset = Width - TextLength(DummyRPort,TimeBuff,Length);

	SetAPen(DummyRPort,DSeg -> TextColour);
	SetBPen(DummyRPort,DSeg -> BackColour);

	SetRast(DummyRPort,1);

	Move(DummyRPort,(Offset >= 0 ? Offset : 0),DummyRPort -> Font -> tf_Baseline);
	Text(DummyRPort,TimeBuff,Length);
}

	/* Ring():
	 *
	 *	This one rings the bell of the alarm clock.
	 */

VOID
Ring(LONG Tea)
{
	struct IOAudio		*AudioBlock;
	struct MsgPort		*ReplyPort;

	struct Screen		 PublicScreen;
	struct Screen		*FirstOne;
	struct Window		*ClockWindow;
	struct IntuiMessage	*Massage;

	ULONG			 Class;
	USHORT			 Code;
	struct Gadget		*ID;

	ULONG			 IntuiLock;
	struct View		*ViewLord;

	SHORT			 DyOffset,PlusY;
	LONG			 TimeOut;

		/* Remember initial first screen. */

	IntuiLock = LockIBase(NULL);
	FirstOne = IntuitionBase -> FirstScreen;
	UnlockIBase(IntuiLock);

		/* Knockin' on heaven's door... */

	OpenWorkBench();

		/* Center the clock window. */

	GetScreenData(&PublicScreen,sizeof(struct Screen),WBENCHSCREEN,NULL);

	NewClockWindow . LeftEdge	= (PublicScreen . Width - NewClockWindow . Width) / 2;
	NewClockWindow . TopEdge	= (PublicScreen . Height - NewClockWindow . Height) / 2;

		/* Open it and paint the background. */

	if(!(ClockWindow = (struct Window *)OpenWindow(&NewClockWindow)))
	{
		DisplayBeep(NULL);
		goto Quit;
	}

	if(NewKick)
		SetAPen(ClockWindow -> RPort,2);
	else
		SetAPen(ClockWindow -> RPort,1);

	RectFill(ClockWindow -> RPort,2,1,ClockWindow -> Width - 3,ClockWindow -> Height - 2);

	RefreshGadgets(&ClockGadget[0],ClockWindow,NULL);

		/* Adjust the contents of the alarm time string. */

	if(Tea)
		Format(ClockTxt[3] . IText,"Alarm time » %2ld:%02ld:%02ld «",DSeg -> AlarmHour,DSeg -> AlarmMinute,DSeg -> AlarmSecond);
	else
		strcpy((char *)ClockTxt[3] . IText,"Countdown elapsed!");

	PrintIText(ClockWindow -> RPort,&ClockTxt[0],0,0);

		/* Allocate some driver memory. */

	if(AudioBlock = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC | MEMF_CLEAR))
	{
			/* Time for a replyport? */

		if(ReplyPort = (struct MsgPort *)CreatePort(NULL,0))
		{
				/* Set up initial driver data. */

			AudioBlock -> ioa_Data					= &AnyChannel[0];
			AudioBlock -> ioa_Length				= 4;
			AudioBlock -> ioa_Request . io_Message . mn_ReplyPort	= ReplyPort;
			AudioBlock -> ioa_Request . io_Message . mn_Node.ln_Pri	= 80;

				/* Allocate the channels on the fly. */

			if(!OpenDevice(AUDIONAME,0,AudioBlock,0))
			{
				AudioBlock -> ioa_Request . io_Command	= CMD_WRITE;
				AudioBlock -> ioa_Request . io_Flags	= ADIOF_PERVOL;
				AudioBlock -> ioa_Period		= 308;
				AudioBlock -> ioa_Volume		= 64;
				AudioBlock -> ioa_Cycles		= 1;
				AudioBlock -> ioa_Data			= (UBYTE *)&RingData[0];
				AudioBlock -> ioa_Length		= 7326;

				IntuiLock = LockIBase(NULL);
				ViewLord = &IntuitionBase -> ViewLord;
				UnlockIBase(IntuiLock);

				DyOffset = ViewLord -> DyOffset;

					/* Ring! */

				BeginIO(AudioBlock);

				WBenchToFront();

				TimeOut = 0;

					/* Ring until somebody clicked our window
					 * or a timeout occurs.
					 */

				FOREVER
				{
						/* Cycles already finished. */

					if(CheckIO(AudioBlock))
						BeginIO(AudioBlock);

					Class = Code = NULL;

					if(Massage = GetMsg(ClockWindow -> UserPort))
					{
						Class = Massage -> Class;
						Code  = Massage -> Code;
						ID    = (struct Gadget *)Massage -> IAddress;

						ReplyMsg(Massage);

						if((Class == GADGETUP && !ID -> GadgetID) || Class == VANILLAKEY)
							break;
					}

					PlusY = 1 - RangeRand(3);

						/* Make the view vibrate. */

					if(DyOffset + PlusY >= 0)
						ViewLord -> DyOffset = DyOffset + PlusY;

					RethinkDisplay();

						/* Wait a tick. */

					Delay(1);

						/* Now for the timeout... */

					if((TimeOut++) >= (TICKS_PER_SECOND * 30))
						break;
				}

				ViewLord -> DyOffset = DyOffset;
				RethinkDisplay();

					/* Still ringing? */

				if(!CheckIO(AudioBlock))
					WaitIO(AudioBlock);

					/* Tick! */

				CloseDevice(AudioBlock);
			}

			DeletePort(ReplyPort);
		}

		FreeMem(AudioBlock,sizeof(struct IOAudio));
	}

		/* Bring original first screen to the front again. */

Quit:	if(FirstOne != ClockWindow -> WScreen)
		ScreenToBack(ClockWindow -> WScreen);

	if(ClockWindow)
		CloseWindow(ClockWindow);
}

	/* ShowTime(ReallyDoIt,Force):
	 *
	 *	Yes, it's Showtime! This one compiles the date/timestring
	 *	and prints it.
	 */

VOID
ShowTime(UBYTE ReallyDoIt,UBYTE Force)
{
	UBYTE TempBuff[30];

	static char *Months[12] =
	{
		"Jan","Feb","Mar",
		"Apr","May","Jun",
		"Jul","Aug","Sep",
		"Oct","Nov","Dec"
	};

	static char *Days[7] =
	{
		"Sun",	/* Note: these have to appear right in this order. */
		"Mon",
		"Tue",
		"Wed",
		"Thu",
		"Fri",
		"Sat"
	};

	static char *LongDays[7] =
	{
		"Sunday",
		"Monday",
		"Tuesday",
		"Wednesday",
		"Thursday",
		"Friday",
		"Saturday"
	};

	static char DateBuff[15],TimeBuff[10];

	static UBYTE LastState = 2;

	static SHORT MonthVectors[14] =
	{
		-1, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364
	};

	LONG JulianDate, Day0, Day1, Day2, Day3;
	LONG Year, Month, Day;

	struct DateStamp Date;

	DateStamp(&Date);

	JulianDate = Date . ds_Days + DDELTA;

	Year = (JulianDate / 146097) * 400;

	Day0 = Day1 = JulianDate %= 146097;

	Year += (JulianDate / 36524) * 100;

	Day2 = Day1 %= 36524;

	Year += (Day2 / 1461) * 4;

	Day3 = Day1 %= 1461;

	Year += Day3 / 365;

	Month = 1 + (Day1 %= 365);

	Day = Month % 30;

	Month /= 30;

	if((Day3 >= 59 && Day0 < 59) || (Day3 < 59 && (Day2 >= 59 || Day0 < 59)))
		Day1++;

	if(Day1 > MonthVectors[1 + Month])
		Month++;

	Day = Day1 - MonthVectors[Month];

	DSeg -> CurrentTime . Year	= Year;
	DSeg -> CurrentTime . Month	= Month;
	DSeg -> CurrentTime . Day	= Day;
	DSeg -> CurrentTime . Weekday	= Date . ds_Days % DAYS_PER_WEEK;

	DSeg -> CurrentTime . Hour	= Date . ds_Minute / MINS_PER_HOUR;
	DSeg -> CurrentTime . Minute	= Date . ds_Minute % MINS_PER_HOUR;
	DSeg -> CurrentTime . Second	= Date . ds_Tick   / TICS_PER_SEC;

		/* Did we need a change? */

	if(LastState != DSeg -> Seconds)
	{
		LastState = DSeg -> Seconds;
		Force = TRUE;
	}

	if(ReallyDoIt)
	{
		if(DSeg -> Seconds)
		{
			Format(TempBuff,"%s %02ld-%s-%02ld %02ld:%02ld:%02ld",
				Days[DSeg -> CurrentTime . Weekday],
				DSeg -> CurrentTime . Day,
				Months[DSeg -> CurrentTime . Month - 1],
				DSeg -> CurrentTime . Year % 100,
				DSeg -> CurrentTime . Hour,
				DSeg -> CurrentTime . Minute,
				DSeg -> CurrentTime . Second);

			PrintIt(TempBuff);
		}
		else
		{
			if(!(Date . ds_Tick / TICKS_PER_SECOND) || Force)
			{
				Format(TempBuff,"%s %02ld-%s-%02ld %02ld:%02ld",
					Days[DSeg -> CurrentTime . Weekday],
					DSeg -> CurrentTime . Day,
					Months[DSeg -> CurrentTime . Month - 1],
					DSeg -> CurrentTime . Year % 100,
					DSeg -> CurrentTime . Hour,
					DSeg -> CurrentTime . Minute);

				SetRast(DummyRPort,1);

				PrintIt(TempBuff);
			}
		}
	}

	if((!DSeg -> CurrentTime . Second || !Printed) && DSeg -> SetEnv)
	{
		Printed = TRUE;

		Format(DateBuff,"%02ld-%s-%02ld",Day,Months[DSeg -> CurrentTime . Month - 1],DSeg -> CurrentTime . Year % 100);
		Format(TimeBuff,"%02ld:%02ld",DSeg -> CurrentTime . Hour,DSeg -> CurrentTime . Minute);

		setenv("DAY",LongDays[DSeg -> CurrentTime . Weekday]);
		setenv("DATE",DateBuff);
		setenv("TIME",TimeBuff);
	}
}

	/* MaxMemSize(MemType):
	 *
	 *	Returns the length of memory block of a special
	 *	kind. Borrowed from Louis A. Mamakos' GfxMem 0.4.
	 */

ULONG
MaxMemSize(ULONG MemType)
{
	ULONG			 BlockSize = 0;
	struct MemHeader	*MemHeader;

	Forbid();

		/* Walk through the memory lists adding the
		 * amount of memory bound to them.
		 */

	for(MemHeader = (struct MemHeader *)SysBase -> MemList . lh_Head ; MemHeader -> mh_Node . ln_Succ ; MemHeader = (struct MemHeader *)MemHeader -> mh_Node . ln_Succ)
	{
		if(MemHeader -> mh_Attributes & MemType)
			BlockSize += ((ULONG)MemHeader -> mh_Upper - (ULONG)MemHeader -> mh_Lower);
	}

	Permit();

	return(BlockSize);
}

	/* FindTheBench():
	 *
	 *	Tries to locate the Workbench screen in the linked list
	 *	of system screens. This is rather a rude method and should
	 *	be exercised only while Intuition is locked.
	 *
	 *	This could be lot easier if using LockPubScreen().
	 */

struct Screen *
FindTheBench()
{
	struct Screen	*WBench;
	ULONG		 IntuiLock = LockIBase(NULL);

		/* Start with the first one. */

	WBench = IntuitionBase -> FirstScreen;

		/* Scan the list... */

	do
	{
			/* The type we want? */

		if((WBench -> Flags & SCREENTYPE) == WBENCHSCREEN)
		{
			UnlockIBase(IntuiLock);
			return(WBench);
		}
	}
	while(WBench = WBench -> NextScreen);

		/* Failed! */

	UnlockIBase(IntuiLock);

	return(NULL);
}

	/* PlayChime():
	 *
	 *	Plays the hour chime.
	 */

LONG
PlayChime()
{
	LONG Rate = 1788,Length = 4,Cycles = 150,Volume = 64 / 2;

	if(ChimeReplyPort)
		return(FALSE);

	ObtainSemaphore(DSeg -> SoundSemaphore);

	if(DSeg -> SoundData && DSeg -> SoundLength)
	{
		ChimeWaveMap	= (UBYTE *)DSeg -> SoundData;
		Length		= DSeg -> SoundLength;
		Volume		= DSeg -> SoundVolume;
		Rate		= DSeg -> SoundRate;
		Cycles		= 1;
	}
	else
		ChimeWaveMap = &SquareWave[0];

		/* Allocate some driver memory. */

	if(!(ChimeAudioBlock = (struct IOAudio *)AllocMem(sizeof(struct IOAudio),MEMF_PUBLIC | MEMF_CLEAR)))
	{
		ReleaseSemaphore(DSeg -> SoundSemaphore);
		return(FALSE);
	}

		/* Time for a replyport? */

	if(!(ChimeReplyPort = (struct MsgPort *)CreatePort(NULL,0)))
	{
		FreeMem(ChimeAudioBlock,sizeof(struct IOAudio));

		ChimeAudioBlock = NULL;

		ReleaseSemaphore(DSeg -> SoundSemaphore);
		return(FALSE);
	}

		/* Set up initial driver data. */

	ChimeAudioBlock -> ioa_Data					= &AnyChannel[0];
	ChimeAudioBlock -> ioa_Length					= 4;
	ChimeAudioBlock -> ioa_Request . io_Message . mn_ReplyPort	= ChimeReplyPort;
	ChimeAudioBlock -> ioa_Request . io_Message . mn_Node . ln_Pri	= 90;

		/* Allocate the channels on the fly. */

	if(OpenDevice(AUDIONAME,0,ChimeAudioBlock,0))
	{
		FreeMem(ChimeAudioBlock,sizeof(struct IOAudio));
		DeletePort(ChimeReplyPort);

		ChimeAudioBlock	= NULL;
		ChimeReplyPort	= NULL;

		ReleaseSemaphore(DSeg -> SoundSemaphore);
		return(FALSE);
	}

	ChimeAudioBlock -> ioa_Request . io_Command	= CMD_WRITE;
	ChimeAudioBlock -> ioa_Request . io_Flags	= ADIOF_PERVOL;
	ChimeAudioBlock -> ioa_Cycles			= Cycles;
	ChimeAudioBlock -> ioa_Data			= ChimeWaveMap;
	ChimeAudioBlock -> ioa_Length			= Length;

	ChimeAudioBlock -> ioa_Period			= Rate;
	ChimeAudioBlock -> ioa_Volume			= Volume;

	BeginIO(ChimeAudioBlock);
}

	/* StopChime():
	 *
	 *	Stops the hour chime.
	 */

VOID
StopChime()
{
	if(ChimeAudioBlock)
	{
		if(ChimeAudioBlock -> ioa_Request . io_Device)
		{
			if(!CheckIO(ChimeAudioBlock))
				WaitIO(ChimeAudioBlock);

			CloseDevice(ChimeAudioBlock);
		}

		if(!DSeg -> SoundData || !DSeg -> SoundLength)
		{
			if(ChimeWaveMap != &SquareWave[0])
				FreeMem(ChimeWaveMap,4);
		}

		FreeMem(ChimeAudioBlock,sizeof(struct IOAudio));
		DeletePort(ChimeReplyPort);

		ChimeAudioBlock	= NULL;
		ChimeReplyPort	= NULL;

		ReleaseSemaphore(DSeg -> SoundSemaphore);
	}
}

	/* FindChunk(ChunkName,FileHandle):
	 *
	 *	Tries to locate an iff-chunk inside a file.
	 */

LONG
FindChunk(ULONG ChunkName,BPTR FileHandle)
{
	LONG 	OldPosition;
	ULONG	FormType = 0;

		/* The format of a typical IFF-chunk. */

	struct
	{
		ULONG	IFF_Type;
		ULONG	IFF_Length;
	} Chunk;

		/* Remember initial file position. */

	OldPosition = Seek(FileHandle,0,OFFSET_CURRENT);

		/* Try to find it. */

	FOREVER
	{
			/* Read the first bytes. */

		if(Read(FileHandle,&Chunk,sizeof(Chunk)) != sizeof(Chunk))
		{
			Seek(FileHandle,OldPosition,OFFSET_BEGINNING);
			return(FALSE);
		}

			/* Is it a FORM-chunk? */

		if(OldPosition == 0 && FormType == 0 && Chunk . IFF_Type == 'FORM')
		{
			Read(FileHandle,&FormType,sizeof(LONG));

				/* Check the form type. */

			if(FormType == ChunkName)
				return(TRUE);

			continue;
		}

			/* Is it the chunk type we want? */

		if(Chunk . IFF_Type == ChunkName)
			return(TRUE);

			/* Skip chunk. */

		Seek(FileHandle,Chunk . IFF_Length,OFFSET_CURRENT);
	}
}

	/* LoadChimeSound(Name):
	 *
	 *	Loads an IFF-8SVX-soundfile to be used as the hour
	 *	chime sound.
	 */

LONG
LoadChimeSound(char *Name)
{
	BPTR SoundHandle;

	APTR SoundData;
	LONG SoundRate,SoundLength,SoundVolume;

		/* The format of the VoiceHeader-chunk. */

	struct
	{
		ULONG	oneShotHiSamples,
			repeatHiSamples,
			samplesPerHiCycle;
		UWORD	samplesPerSec;
		UBYTE	ctOctave,
			sCompression;
		LONG	volume;
	} VoiceHeader;

		/* No name? Reset to defaults. */

	if(!RexxStrCmp(Name,"OFF"))
	{
		ObtainSemaphore(DSeg -> SoundSemaphore);

			/* Free the memory. */

		if(DSeg -> SoundLength && DSeg -> SoundData)
			FreeMem(DSeg -> SoundData,DSeg -> SoundLength);

			/* Mark sound slot as vacant. */

		DSeg -> SoundLength	= 0;
		DSeg -> SoundData	= NULL;

		ReleaseSemaphore(DSeg -> SoundSemaphore);

		return(TRUE);
	}

		/* Open the file for reading. */

	if(!(SoundHandle = Open(Name,MODE_OLDFILE)))
		return(FALSE);

		/* Is it a sound file? */

	if(!FindChunk('8SVX',SoundHandle))
	{
		Close(SoundHandle);

		return(FALSE);
	}

		/* Look for the VoiceHeader. */

	if(!FindChunk('VHDR',SoundHandle))
	{
		Close(SoundHandle);
		return(FALSE);
	}

		/* Read the header. */

	if(Read(SoundHandle,&VoiceHeader,sizeof(VoiceHeader)) != sizeof(VoiceHeader))
	{
		Close(SoundHandle);
		return(FALSE);
	}

		/* Fill in the more important information. */

	SoundLength	= VoiceHeader . oneShotHiSamples + VoiceHeader . repeatHiSamples;
	SoundRate	= ((GfxBase -> DisplayFlags & PAL) ? 3546895 : 3579545) / VoiceHeader . samplesPerSec;
	SoundVolume	= (VoiceHeader . volume > 64 ? 64 : VoiceHeader . volume);

		/* Proceed with the body chunk. */

	if(!FindChunk('BODY',SoundHandle))
	{
		Close(SoundHandle);
		return(FALSE);
	}

		/* Allocate space for the sound data. */

	if(!(SoundData = (APTR)AllocMem(SoundLength,MEMF_PUBLIC | MEMF_CHIP)))
	{
		Close(SoundHandle);
		return(FALSE);
	}

		/* Read the sound data. */

	if(Read(SoundHandle,SoundData,SoundLength) != SoundLength)
	{
		FreeMem(SoundData,SoundLength);
		Close(SoundHandle);
		return(FALSE);
	}

		/* Close the file. */

	Close(SoundHandle);

		/* Lock access to sound data. */

	ObtainSemaphore(DSeg -> SoundSemaphore);

		/* Free last sound. */

	if(DSeg -> SoundLength && DSeg -> SoundData)
		FreeMem(DSeg -> SoundData,DSeg -> SoundLength);

		/* Fill in the data. */

	DSeg -> SoundData	= SoundData;
	DSeg -> SoundLength	= SoundLength;
	DSeg -> SoundRate	= SoundRate;
	DSeg -> SoundVolume	= SoundVolume;

		/* Unlock it again. */

	ReleaseSemaphore(DSeg -> SoundSemaphore);

	return(TRUE);
}

	/* CheckDClockStatus(Arg):
	 *
	 *	Checks DClock option flags and returns them.
	 */

STRPTR
CheckDClockStatus(STRPTR Arg)
{
		/* Static result string. */

	STATIC UBYTE Response[200];

		/* Every option to be checked. */

	STATIC STRPTR Options[17] =
	{
		"BEEP",
		"CLICK",
		"CLICKVOLUME",
		"PRIORITY",
		"TEXTCOLOUR",
		"BACKCOLOUR",
		"ALARM",
		"ALARMTIME",
		"SETENV",
		"VERSION",
		"COUNTDOWN",
		"HOUR",
		"SECONDS",
		"SOUND",
		"PAGE",
		"SPEECH",
		"LINE"
	};

		/* A temporary string. */

	UBYTE TempString[20];

	LONG i,TheOption = -1;

		/* Clear the string. */

	Response[0] = 0;

		/* Check which option matches the argument.
		 * If none matches we'll produce a string
		 * filled with all options.
		 */

	for(i = 0 ; i < 16 ; i++)
	{
		if(!RexxStrCmp(Arg,Options[i]))
		{
			TheOption = i;
			break;
		}
	}

		/* Is it BEEP? */

	if(TheOption == 0 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response,Options[0]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Beep ? "ON" : "OFF"));
	}

		/* Is it CLICK? */

	if(TheOption == 1 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[1]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Click ? "ON" : "OFF"));
	}

		/* Is it CLICKVOLUME? */

	if(TheOption == 2 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[2]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> ClickVolume,TempString);

		strcat(Response,TempString);
	}

		/* Is it PRIORITY? */

	if(TheOption == 3 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[3]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> Priority,TempString);

		strcat(Response,TempString);
	}

		/* Is it TEXTCOLOUR? */

	if(TheOption == 4 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[4]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> TextColour,TempString);

		strcat(Response,TempString);
	}

		/* Is it BACKCOLOUR? */

	if(TheOption == 5 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[5]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> Priority,TempString);

		strcat(Response,TempString);
	}

		/* Is it ALARM? */

	if(TheOption == 6 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[6]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Alarm ? "ON" : "OFF"));
	}

		/* Is it ALARMTIME? */

	if(TheOption == 7 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[7]);
			strcat(Response," ");
		}

		Format(TempString,"%02ld:%02ld:%02ld",DSeg -> AlarmHour,DSeg -> AlarmMinute,DSeg -> AlarmSecond);

		strcat(Response,TempString);
	}

		/* Is it SETENV? */

	if(TheOption == 8 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[8]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> SetEnv ? "ON" : "OFF"));
	}

		/* Is it VERSION? */

	if(TheOption == 9 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[9]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> Revision,TempString);

		strcat(Response,TempString);
	}

		/* Is it COUNTDOWN? */

	if(TheOption == 10 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[10]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> Countdown,TempString);

		strcat(Response,TempString);
	}

		/* Is it HOUR? */

	if(TheOption == 11 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[11]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Hour ? "ON" : "OFF"));
	}

		/* Is it SECONDS? */

	if(TheOption == 12 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[12]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Seconds ? "ON" : "OFF"));
	}

		/* Is it SOUND? */

	if(TheOption == 13 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[13]);
			strcat(Response," ");
		}

		strcat(Response,((DSeg -> SoundData && DSeg -> SoundLength) ? "ON" : "OFF"));
	}

		/* Is it PAGE? */

	if(TheOption == 14 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[14]);
			strcat(Response," ");
		}

		BuildValueString(DSeg -> Page,TempString);

		strcat(Response,TempString);
	}

		/* Is it SPEECH? */

	if(TheOption == 15 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[15]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Speech ? "ON" : "OFF"));
	}

		/* Is it LINE? */

	if(TheOption == 16 || TheOption == -1)
	{
		if(TheOption == -1)
		{
			strcat(Response," ");
			strcat(Response,Options[16]);
			strcat(Response," ");
		}

		strcat(Response,(DSeg -> Online ? "ON" : "OFF"));
	}

		/* Return the result of our 'questionnaire'. */

	return(Response);
}

	/* RexxServer():
	 *
	 *	Rexx server subtask (process), handles the rexx commands
	 *	asynchronously.
	 */

VOID
RexxServer()
{
	struct RexxMessage	*RexxMsg;	/* Rexx signal message. */
	ULONG			 SignalSet;	/* Incoming signals. */

	STRPTR			 StringResult;	/* Result string (error message). */
	LONG			 NumResult;	/* Return code. */

	LONG			 ArgCount;	/* String counter. */

	UBYTE Arg1[20],Arg2[20];		/* Both arguments. */

		/* Get the base register (hey Manx, where's
		 * the __saveds equivalent?).
		 */

	geta4();

		/* Create the communication port. */

	if(!(RexxTaskPort = (struct MsgPort *)CreatePort(NULL,0)))
		goto Quit;

		/* Synchronization. */

	Signal(HandlerProcess,SIG_SHAKE);

		/* Ad infinitum. */

	FOREVER
	{
			/* Wait for a signal. */

		SignalSet = Wait(SIG_CLOSE | (1 << RexxTaskPort -> mp_SigBit));

			/* A rexx message? */

		if(SignalSet & (1 << RexxTaskPort -> mp_SigBit))
		{
				/* Intercept all messages. */

			while(RexxMsg = GetMsg(RexxTaskPort))
			{
				ArgCount = 0;

					/* Get the command string. */

				StringResult = GetRexxCommand(RexxMsg);

					/* Get command and argument. */

				GetToken(StringResult,&ArgCount,Arg1,20);
				GetToken(StringResult,&ArgCount,Arg2,20);

					/* Zero default values. */

				StringResult	= NULL;
				NumResult	= 0;

					/* Adjust Click volume? */

				if(!RexxStrCmp(Arg1,"CLICKVOLUME"))
				{
					if(GetStringValue(Arg2) > 64 || GetStringValue(Arg2) < 0)
						NumResult = 10;
					else
						DSeg -> ClickVolume = GetStringValue(Arg2);
				}

					/* Adjust task priority? */

				if(!RexxStrCmp(Arg1,"PRIORITY"))
				{
					if(GetStringValue(Arg2) > 127 || GetStringValue(Arg2) < -128)
						NumResult = 10;
					else
					{
						DSeg -> Priority = GetStringValue(Arg2);
						SetTaskPri(DSeg -> Child,DSeg -> Priority);
					}
				}

					/* Turn DisplayBeep() off? */

				if(!RexxStrCmp(Arg1,"BEEP"))
				{
					if(!RexxStrCmp(Arg2,"OFF"))
						DSeg -> Beep = FALSE;
					else
					{
						if(!RexxStrCmp(Arg2,"ON"))
							DSeg -> Beep = TRUE;
						else
							NumResult = 10;
					}
				}

					/* Turn keyboard click off? */

				if(!RexxStrCmp(Arg1,"CLICK"))
				{
					if(!RexxStrCmp(Arg2,"OFF"))
						DSeg -> Click = FALSE;
					else
					{
						if(!RexxStrCmp(Arg2,"ON"))
							DSeg -> Click = TRUE;
						else
							NumResult = 10;
					}
				}

					/* Set text colour? */

				if(!RexxStrCmp(Arg1,"TEXTCOLOUR"))
					DSeg -> TextColour = GetStringValue(Arg2);

					/* Set background colour? */

				if(!RexxStrCmp(Arg1,"BACKCOLOUR"))
					DSeg -> BackColour = GetStringValue(Arg2);

					/* Turn alarm on? */

				if(!RexxStrCmp(Arg1,"ALARM"))
				{
					if(!RexxStrCmp(Arg2,"OFF"))
						DSeg -> Alarm = FALSE;
					else
					{
						if(!RexxStrCmp(Arg2,"ON"))
							DSeg -> Alarm = TRUE;
						else
							NumResult = 10;
					}
				}

					/* Set alarm time? */

				if(!RexxStrCmp(Arg1,"ALARMTIME"))
				{
					char TimeBuff[3];
					LONG TheTime;

					TimeBuff[2] = 0;

					TimeBuff[0] = Arg2[0];
					TimeBuff[1] = Arg2[1];

					TheTime = GetStringValue(TimeBuff);

					if(TheTime >= 0 && TheTime <= 23)
						DSeg -> AlarmHour = TheTime;
					else
						NumResult = 10;

					TimeBuff[0] = Arg2[3];
					TimeBuff[1] = Arg2[4];

					TheTime = GetStringValue(TimeBuff);

					if(TheTime >= 0 && TheTime <= 59)
						DSeg -> AlarmMinute = TheTime;
					else
						NumResult = 10;

					TimeBuff[0] = Arg2[6];
					TimeBuff[1] = Arg2[7];

					TheTime = GetStringValue(TimeBuff);

					if(TheTime >= 0 && TheTime <= 59)
						DSeg -> AlarmSecond = TheTime;
					else
						NumResult = 10;
				}

					/* Set environment variables? */

				if(!RexxStrCmp(Arg1,"SETENV"))
				{
					if(!RexxStrCmp(Arg2,"OFF"))
						DSeg -> SetEnv = FALSE;
					else
					{
						if(!RexxStrCmp(Arg2,"ON"))
							DSeg -> SetEnv = TRUE;
						else
							NumResult = 10;
					}
				}

					/* Set the countdown tea timer? */

				if(!RexxStrCmp(Arg1,"COUNTDOWN"))
				{
					if(GetStringValue(Arg2) > 0)
						DSeg -> Countdown = GetStringValue(Arg2);
					else
						NumResult = 10;
				}

					/* Refresh the display? */

				if(!RexxStrCmp(Arg1,"REFRESH"))
				{
					if(CloseWorkBench())
						OpenWorkBench();
				}

					/* Turn hour alarm on? */

				if(!RexxStrCmp(Arg1,"HOUR"))
				{
					if(!RexxStrCmp(Arg2,"OFF"))
						DSeg -> Hour = FALSE;
					else
					{
						if(!RexxStrCmp(Arg2,"ON"))
							DSeg -> Hour = TRUE;
						else
							NumResult = 10;
					}
				}

					/* Turn keyboard click off? */

				if(!RexxStrCmp(Arg1,"SECONDS"))
				{
					if(!RexxStrCmp(Arg2,"OFF"))
						DSeg -> Seconds = FALSE;
					else
					{
						if(!RexxStrCmp(Arg2,"ON"))
							DSeg -> Seconds = TRUE;
						else
							NumResult = 10;
					}
				}

					/* Load a sound file? */

				if(!RexxStrCmp(Arg1,"SOUND"))
				{
					if(!LoadChimeSound(Arg2))
						NumResult = 10;
				}

					/* Get a DClock status. */

				if(!RexxStrCmp(Arg1,"STATUS"))
					StringResult = CheckDClockStatus(Arg2);

					/* Show a special page? */

				if(!RexxStrCmp(Arg1,"PAGE"))
				{
					if(GetStringValue(Arg2) > 4 || GetStringValue(Arg2) < 0 || (GetStringValue(Arg2) == 4 && DSeg -> Countdown < 1))
						NumResult = 10;
					else
						DSeg -> Page = GetStringValue(Arg2);
				}

					/* Tell the current time? */

				if(!RexxStrCmp(Arg1,"TELLTIME"))
				{
					if(!SpeechProcess)
					{
						Forbid();

						if(SpeechProcess = CreateFuncProc("DClock-Speech",5,SpeechServer,4000))
							Wait(SIG_SHAKE);

						Permit();
					}

					if(SpeechProcess)
						Signal(SpeechProcess,SIG_CLICK);
				}

					/* Read the clock chip? */

				if(!RexxStrCmp(Arg1,"READTIME"))
				{
					if(!ReadClock())
						NumResult = 10;
				}

					/* Reply the rexx command. */

				ReplyRexxCommand(RexxMsg,NumResult,0,StringResult);
			}
		}

			/* Remove the server task. */

		if(SignalSet & SIG_CLOSE)
			break;
	}

		/* Delete the port. */

Quit:	if(RexxTaskPort)
		DeletePort(RexxTaskPort);

		/* Hey, I'm done! */

	Signal(HandlerProcess,SIG_SHAKE);
}

	/* SpeechServer():
	 *
	 *	Asks the narrator device to tell the time.
	 */

VOID
SpeechServer()
{
	ULONG			 SignalSet;		/* Signal mask. */
	struct MsgPort		*NarratorPort;		/* Narrator reply port. */
	struct narrator_rb	*NarratorRequest;	/* Narrator device link. */

	char			 WorkString[40];	/* Phoneme string. */

		/* Numbers above twenty. */

	static char *AboveTwenty[4] =
	{
		"TWEH4NTIY4",
		"THER4TIY4",
		"FOH4RTIY4",
		"FIH4FTIY4"
	};

		/* Numbers from zero to nineteen. */

	static char *SingleNumbers[20] =
	{
		"ZIY4ROW",
		"WAH4N",
		"TUW4",
		"THRIY4",
		"FOH4R",
		"FAY4V",
		"SIH4KKSZ",
		"SEH4VVEHN",
		"EY4T",
		"NAY4N",
		"TEH4N",
		"IY4LAEEHFAEEHN",
		"TWEH4LF",
		"THER4TIY4N",
		"FOH4RTIY4N",
		"FIH4FTIY4N",
		"SIH4KKSZTIY4N",
		"SEH4VVEHNTIY4N",
		"EY3TIY4N",
		"NAY4NTIY4N"
	};

		/* The two halves of the day. */

	static char *TimeOfDay[2] =
	{
		" EY5EH3M.",
		" BPIY5EH3M."
	};

		/* Get the data base register. */

	geta4();

		/* Allocate speech driver data. */

	if(NarratorPort = CreatePort(NULL,0))
	{
		if(NarratorRequest = (struct narrator_rb *)CreateExtIO(NarratorPort,sizeof(struct narrator_rb)))
		{
				/* Say which channels we need. */

			NarratorRequest -> ch_masks		= &AnyChannel[0];
			NarratorRequest -> nm_masks		= 4;

				/* This is a write request. */

			NarratorRequest -> message . io_Command	= CMD_WRITE;
			NarratorRequest -> message . io_Data	= (APTR)WorkString;

				/* Try to open the speech driver. */

			if(!OpenDevice("narrator.device",0,NarratorRequest,0))
			{
					/* Handshake with handler process. */

				Signal(HandlerProcess,SIG_SHAKE);

				FOREVER
				{
						/* Wait for signal. */

					SignalSet = Wait(SIG_CLOSE | SIG_CLICK);

						/* Are we to shut down? */

					if(SignalSet & SIG_CLOSE)
						break;

						/* Prevent time data from being changed. */

					Forbid();

					strcpy(WorkString,"IH4THZ ");

						/* Is the hour below twelve? */

					if(DSeg -> CurrentTime . Hour <= 12)
						strcat(WorkString,SingleNumbers[DSeg -> CurrentTime . Hour]);

						/* Is it above twelve? */

					if(DSeg -> CurrentTime . Hour > 12)
						strcat(WorkString,SingleNumbers[DSeg -> CurrentTime . Hour - 12]);

					strcat(WorkString," EH4KLAA4K ");

						/* Are the minutes above zero and below twenty? */

					if(DSeg -> CurrentTime . Minute < 20 && DSeg -> CurrentTime . Minute > 0)
						strcat(WorkString,SingleNumbers[DSeg -> CurrentTime . Minute]);

						/* Are the minutes above twenty? */

					if(DSeg -> CurrentTime . Minute >= 20)
					{
							/* Append the tenths first. */

						strcat(WorkString,AboveTwenty[DSeg -> CurrentTime . Minute / 10 - 2]);

							/* Add the minutes if any. */

						if(DSeg -> CurrentTime . Minute % 10)
							strcat(WorkString,SingleNumbers[DSeg -> CurrentTime . Minute % 10]);
					}

						/* Add a.m./p.m. */

					if(DSeg -> CurrentTime . Hour > 12)
						strcat(WorkString,TimeOfDay[1]);
					else
					{
						if(DSeg -> CurrentTime . Hour > 0 && DSeg -> CurrentTime . Hour < 12)
							strcat(WorkString,TimeOfDay[0]);
					}

					Permit();

						/* Remember the length (-1 doesn't work here for some reason). */

					NarratorRequest -> message . io_Length = strlen(WorkString);

						/* Say it and wait for it to terminate. */

					SendIO(NarratorRequest);
					WaitIO(NarratorRequest);

					GetMsg(NarratorPort);
				}

					/* Deallocate our resources and exit. */
				
				while(!CheckIO(NarratorRequest))
				{
					AbortIO(NarratorRequest);
					WaitIO(NarratorRequest);

					GetMsg(NarratorPort);
				}

				CloseDevice(NarratorRequest);
			}

			DeleteExtIO(NarratorRequest);
		}

		DeletePort(NarratorPort);
	}

		/* Lock & quit. */

	Forbid();

	SpeechProcess = NULL;

	Signal(HandlerProcess,SIG_SHAKE);
}

	/* CreateFuncProc():
	 *
	 *	Similar to CreateTask this routine will start a
	 *	'C' function as a process. The technique used
	 *	by this nifty little piece of code was originally
	 *	conceived by Leo Schwab.
	 */

struct Process *
CreateFuncProc(char *Name,LONG Priority,APTR InitCode,ULONG StackSize)
{
	struct Process *ChildProc = NULL;

	struct
	{
		ULONG	SegLength;	/* Length of segment. */
		BPTR	NextSeg;	/* Pointer to next segment. */

		WORD	FirstCode;	/* First instruction (JMP). */
		APTR	RealCode;	/* Address of function. */
	} *FakeSeg;

		/* Allocate the segment. */

	if(FakeSeg = (struct FakeSeg *)AllocMem(sizeof(*FakeSeg),MEMF_PUBLIC | MEMF_CLEAR))
	{
		struct MsgPort *ChildPort;

			/* Fill in the data. */

		FakeSeg -> SegLength	= sizeof(*FakeSeg);
		FakeSeg -> FirstCode	= 0x4EF9;		/* JMP EA */
		FakeSeg -> RealCode	= InitCode;		/* EA */

			/* Create the process. */

		if(ChildPort = (struct MsgPort *)CreateProc(Name,Priority,MKBADDR(&FakeSeg -> NextSeg),StackSize))
			ChildProc = (struct Process *)ChildPort -> mp_SigTask;

		Delay(TICKS_PER_SECOND / 2);

			/* Free the segment. */

		FreeMem(FakeSeg,sizeof(*FakeSeg));
	}

		/* Return the pointer to the process. */

	return(ChildProc);
}

	/* CreateDummyRPort():
	 *
	 *	Creates a hidden RastPort for time/graphics
	 *	rendering.
	 */

BYTE
CreateDummyRPort()
{
	struct TextAttr	FontRequest;
	SHORT		i;

		/* Allocate the BitMap. */

	if(!(DummyMap = (struct DummyMap *)AllocMem(sizeof(struct BitMap),MEMF_PUBLIC | MEMF_CLEAR)))
		return(FALSE);

		/* How many bitplanes are there in the display? */

	DummyDepth = Window -> RPort -> BitMap -> Depth;

		/* Initialize the bitmap pointers. */

	InitBitMap(DummyMap,DummyDepth,Width,Height);

		/* Allocate the memory. */

	for(i = 0 ; i < DummyDepth ; i++)
	{
		if(!(DummyMap -> Planes[i] = AllocMem(Byte(Width) * Height,MEMF_CHIP)))
			return(FALSE);
	}

		/* Mangle the RastPort. */

	if(!(DummyRPort = (struct RastPort *)AllocMem(sizeof(struct RastPort),MEMF_PUBLIC | MEMF_CLEAR)))
		return(FALSE);

		/* Initialize the RastPort. */

	InitRastPort(DummyRPort);

		/* Link it to the BitMap. */

	DummyRPort -> BitMap = DummyMap;

		/* Clear the drawing area. */

	SetRast(DummyRPort,1);

		/* Copy the font attributes. */

	FontRequest . ta_Name	= (STRPTR)Window -> IFont -> tf_Message . mn_Node . ln_Name;
	FontRequest . ta_YSize	= Window -> IFont -> tf_YSize;
	FontRequest . ta_Style	= Window -> IFont -> tf_Style;
	FontRequest . ta_Flags	= Window -> IFont -> tf_Flags;

		/* Can we open the font (is it already in the system
		 * list)?
		 */

	if(!(DummyFont = (struct TextFont *)OpenFont(&FontRequest)))
	{
			/* It is probably a diskfont. */

		if(DiskfontBase = (struct Library *)OpenLibrary("diskfont.library",0))
		{
			if(!(DummyFont = (struct TextFont *)OpenDiskFont(&FontRequest)))
				return(FALSE);
			else
				CloseLibrary(DiskfontBase);
		}
	}

		/* Attach the font to the RastPort. */

	SetFont(DummyRPort,DummyFont);

		/* And set the drawing mode. */

	SetDrMd(DummyRPort,JAM2);

	return(TRUE);
}

	/* DeleteDummyRPort():
	 *
	 *	Removes the hidden RastPort from memory and get rid
	 *	of the font attached to it.
	 */

VOID
DeleteDummyRPort()
{
	SHORT i;

	if(DummyMap)
	{
		for(i = 0 ; i < DummyDepth ; i++)
			if(DummyMap -> Planes[i])
				FreeMem(DummyMap -> Planes[i],Byte(Width) * Height);

		FreeMem(DummyMap,sizeof(struct BitMap));

		DummyMap = NULL;
	}

	if(DummyRPort)
	{
		FreeMem(DummyRPort,sizeof(struct RastPort));
		DummyRPort = NULL;
	}

	if(DummyFont)
	{
		CloseFont(DummyFont);
		DummyFont = NULL;
	}
}

	/* MaxFontWidth():
	 *
	 *	Calculate the maximum width of the display by looking for
	 *	the widest letter.
	 */

UBYTE
MaxFontWidth()
{
	BYTE	FontWidth = 0,TempWidth;
	char	Shuttle[2];
	SHORT	i;

		/* We cannot use a single character to check the width
		 * of a letter, so we'll fake a string.
		 */

	Shuttle[1] = 0;

		/* Check all letters. */

	for(i = 0 ; i < 256 ; i++)
	{
		Shuttle[0] = i;

			/* Is it wider than the last letter. */

		if((TempWidth = TextLength(Window -> RPort,Shuttle,1)) > FontWidth)
			FontWidth = TempWidth;
	}

	return(FontWidth);
}

	/* ShutDown(HandShake):
	 *
	 *	Closes DClock and waits for removal.
	 */

VOID
ShutDown(LONG HandShake)
{
		/* Restore system functions. */

	if(OldDisplayBeep)
		SetFunction((struct Library *)IntuitionBase,-0x60,OldDisplayBeep);

	if(OldCloseWBench)
		SetFunction((struct Library *)IntuitionBase,-0x4E,OldCloseWBench);

	FlushHandler();
	FlushSound();

	if(Window)
	{
		SetRast(DummyRPort,1);

		ClipBlit(DummyRPort,0,0,Window -> WScreen -> BarLayer -> rp,LeftEdge,1,Width,Height,0xC0);

		CloseWindow(Window);
	}

	DeleteDummyRPort();

		/* Return the special signals. */

	if(BenchSig != -1)
		FreeSignal(BenchSig);

	if(DisplaySig != 1)
		FreeSignal(DisplaySig);

	if(SpeechSig != 1)
		FreeSignal(SpeechSig);

		/* Free the Rexx host port. */

	if(DSeg -> RexxHost)
		DeleteRexxHost(DSeg -> RexxHost);

	if(RexxProcess)
	{
		Signal(RexxProcess,SIG_CLOSE);

		Wait(SIG_SHAKE);
	}

	if(SpeechProcess)
	{
		Signal(SpeechProcess,SIG_CLOSE);

		Wait(SIG_SHAKE);
	}

	if(RexxHostBase)
		CloseLibrary(RexxHostBase);

	if(IntuitionBase)
		CloseLibrary(IntuitionBase);

	if(GfxBase)
		CloseLibrary(GfxBase);

		/* Sneak out before someone can
		 * UnLoadSeg() us.
		 */

	Forbid();

		/* Goodbye father. */

	if(DSeg -> Father)
		Signal(DSeg -> Father,HandShake);
}

	/* _main():
	 *
	 *	Modified Aztec C startup-routine, no CLI parsing,
	 *	no Workbench parsing, absolutely nothing.
	 */

LONG
_main()
{
	volatile ULONG		 MemSize,MaxSize;

	ULONG			 SignalSet;
	UBYTE			 Force,Blink = FALSE,i,EmptyWidth,FullWidth;

	LONG			 RexxMask = 0;

	struct Screen		*Workbench;

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

		/* If somebody called us from CLI... */

	if(HandlerProcess -> pr_CLI)
		return(-1);

		/* Is the DSeg structure anywhere? */

	if(!(DSeg = (struct DSeg *)FindPort(PORTNAME)))
	{
		Forbid();
		Signal(DSeg -> Father,DSeg -> RingBack);

		return;
	}

		/* Check if it's below our current revision
		 * number (probably doesn't support some
		 * structure tags).
		 */

	if(DSeg -> Revision < REVISION)
	{
		Forbid();
		Signal(DSeg -> Father,DSeg -> RingBack);

		return;
	}

		/* Open those libraries. */

	if(!(IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library",0)))
	{
		Forbid();
		Signal(DSeg -> Father,DSeg -> RingBack);

		return;
	}

	if(!(GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",0)))
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

		/* Open RexxHostLibrary if possible. If the open
		 * fails, it doesn't matter.
		 */

	RexxHostBase = OpenLibrary("rexxhost.library",34);

		/* Try to find the Workbench screen. */

	if(!(Workbench = (struct Screen *)FindTheBench()))
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

		/* Open the backdrop window. */

	if(!(Window = (struct Window *)OpenWindow(&NewWindow)))
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

		/* Initialize the click sound data. */

	if(!InitSound())
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

		/* Install the handler code. */

	if(!InitHandler())
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

	if((BenchSig = AllocSignal(-1)) == -1)
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

	if((DisplaySig = AllocSignal(-1)) == -1)
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

	if((SpeechSig = AllocSignal(-1)) == -1)
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

		/* Adjust the window left offset. */

	Width		= MaxFontWidth() * 22;
	Height		= Window -> IFont -> tf_YSize;

	LeftEdge	= Workbench -> Width - 55 - Width;

	EmptyWidth	= TextLength(Window -> RPort,"E",1);
	FullWidth	= TextLength(Window -> RPort,"F",1);

		/* Create the dummy RastPort. */

	if(!CreateDummyRPort())
	{
		ShutDown(DSeg -> RingBack);

		return;
	}

		/* Check if we are running unter Kickstart 2.x. */

	if(SysBase -> LibNode . lib_Version >= 36)
	{
		USHORT TempCol;

		NewKick = TRUE;

		for(i = 0 ; i < 228 ; i++)
		{
			TempCol = ClockMap[i];
			ClockMap[i] = ClockMap[i + 228];
			ClockMap[i + 228] = TempCol;
		}

		for(i = 0 ; i < 3 ; i++)
			ClockTxt[i] . FrontPen = 1;

		LeftEdge = Workbench -> Width - 25 - Width;
	}

		/* Create the rexx server task. */

	if(RexxHostBase)
	{
		if(!(RexxProcess = (struct Process *)CreateFuncProc("DClock-Rexx",5,RexxServer,4000)))
		{
Shut:			ShutDown(DSeg -> RingBack);

			return;
		}

			/* Wait for handshake. */

		Wait(SIG_SHAKE);

			/* No port? Fail fast! */

		if(!RexxTaskPort)
			goto Shut;
	}

		/* Fill in the window and bring it to the front. */

	ShowTime(FALSE,TRUE);

		/* Patch system function. */

	OldDisplayBeep = SetFunction((struct Library *)IntuitionBase,-0x60,NewDisplayBeep);
	OldCloseWBench = SetFunction((struct Library *)IntuitionBase,-0x4E,NewCloseWBench);

		/* If RexxHostBase is around, add the rexx port. */

	if(RexxHostBase)
	{
		if(DSeg -> RexxHost = CreateRexxHost("DCLOCK"))
			RexxMask = (1 << DSeg -> RexxHost -> rh_Port . mp_SigBit);
	}

		/* Initialize the signal-semaphore. */

	InitSemaphore(DSeg -> SoundSemaphore);

		/* Now we are truly running. */

	DSeg -> Child = SysBase -> ThisTask;

		/* How much memory is there around in this
		 * Amiga?
		 */

	MaxSize = MaxMemSize(MEMF_CHIP) + MaxMemSize(MEMF_FAST);

		/* Tell father to finish. */

	Signal(DSeg -> Father,DSeg -> RingBack);

	DSeg -> Father = NULL;

		/* Go into infinite loop waiting for signals. */

	FOREVER
	{
		SignalSet = Wait(SIG_TIMER | SIG_CLOSE | SIG_CLICK | SIG_TOGGL | SIG_BENCH | SIG_WINDO | SIG_DISPL | SIG_SPEECH | RexxMask);

			/* Was it a Rexx call? */

		if((SignalSet & RexxMask) && RexxMask)
		{
			struct RexxMessage *RexxMsg;

				/* Capture the messages... */

			while(RexxMsg = GetMsg((struct MsgPort *)DSeg -> RexxHost))
			{
					/* Send them to the rexx server task. */

				if(GetRexxCommand(RexxMsg))
					PutMsg(RexxTaskPort,RexxMsg);
				else
					FreeRexxCommand(RexxMsg);
			}
		}

			/* Change the display mode? */

		if(SignalSet & SIG_TOGGL)
		{
			DSeg -> Page++;

			SetRast(DummyRPort,1);

			if(DSeg -> Page == 5)
				DSeg -> Page = 0;

			if(DSeg -> Countdown < 1 && DSeg -> Page == 4)
				DSeg -> Page = 0;

			Force = TRUE;
		}

			/* Are we to click? */

		if((SignalSet & SIG_CLICK) && DSeg -> Click)
			Click();

			/* Are we to shut down? */

		if(SignalSet & SIG_CLOSE)
		{
			if(ChimeAudioBlock)
				StopChime();

			ShutDown(SIG_CLOSE);
			return;
		}

		if(SignalSet & SIG_BENCH)
		{
				/* Close the window... */

			if(Window)
			{
				CloseWindow(Window);
				Window = NULL;

				DeleteDummyRPort();

				Delay(25);

				FOREVER
				{
						/* Wait for wakeup call... */

					if((SetSignal(NULL,NULL) & SIG_CLOSE) == SIG_CLOSE)
					{
						SetSignal(NULL,SIG_CLOSE);

						SignalSet = SIG_CLOSE;
						break;
					}

					if(Workbench = (struct Screen *)FindTheBench())
					{
						SignalSet = NULL;
						break;
					}

					Delay(25);
				}
			}

				/* Finish? */

			if(SignalSet & SIG_CLOSE)
			{
				if(ChimeAudioBlock)
					StopChime();

				ShutDown(SIG_CLOSE);
				return;
			}

				/* Open the window. */

			if(!(Window = (struct Window *)OpenWindow(&NewWindow)))
			{
BeatIt:				if(ChimeAudioBlock)
					StopChime();

				ShutDown(SIG_CLOSE);
				return;
			}

			Width	= MaxFontWidth() * 22;
			Height	= Window -> IFont -> tf_YSize;

				/* Re-adjust the Window offset. */

			if(NewKick)
				LeftEdge = Workbench -> Width - 25 - Width;
			else
				LeftEdge = Workbench -> Width - 55 - Width;

			EmptyWidth	= TextLength(Window -> RPort,"E",1);
			FullWidth	= TextLength(Window -> RPort,"F",1);

				/* Redraw the time if necessary. */

			Printed = FALSE;

			if(!CreateDummyRPort())
				goto BeatIt;
		}

			/* A window refresh call came in. */

		if(SignalSet & SIG_WINDO)
		{
			struct IntuiMessage *IMsg;

				/* Reply the Message (don't need it). */

			if(IMsg = GetMsg(Window -> UserPort))
				ReplyMsg(IMsg);
		}

			/* Give it a brief beep to indicate the hour? */

		if(DSeg -> Hour && !DSeg -> CurrentTime . Minute && !DSeg -> CurrentTime . Second)
			PlayChime();

			/* Check if chime has ended. */

		if(ChimeAudioBlock)
		{
			if(CheckIO(ChimeAudioBlock))
				StopChime();
		}

			/* Ready to ring the bell? */

		if(DSeg -> CurrentTime . Hour   == DSeg -> AlarmHour &&
		   DSeg -> CurrentTime . Minute == DSeg -> AlarmMinute &&
		   DSeg -> CurrentTime . Second == DSeg -> AlarmSecond &&
		   DSeg -> Alarm)
			Ring(TRUE);

			/* If needed, decrement the tea timer. */

		if(DSeg -> Countdown > 0)
		{
			if(!(--DSeg -> Countdown))
				Ring(FALSE);
		}

			/* Check the display signal before we
			 * redraw it.
			 */

		if(SignalSet & SIG_DISPL)
		{
			DSeg -> Seconds ^= TRUE;

			Force = TRUE;
		}

			/* We are to tell the time. */

		if(SignalSet & SIG_SPEECH)
		{
				/* Remove speech process if speech
				 * has been turned off after it has
				 * been run once.
				 */

			if(!DSeg -> Speech)
			{
				if(SpeechProcess)
				{
					Signal(SpeechProcess,SIG_CLOSE);

					Wait(SIG_SHAKE);
				}
			}
			else
			{
					/* Try to create the speech process. */

				if(!SpeechProcess)
				{
					Forbid();

					if(SpeechProcess = CreateFuncProc("DClock-Speech",5,SpeechServer,4000))
						Wait(SIG_SHAKE);

					Permit();
				}

					/* Tell the time. */

				if(SpeechProcess)
					Signal(SpeechProcess,SIG_CLICK);
			}
		}

			/* Show time and date. */

		if(DSeg -> Page == 0)
			ShowTime(TRUE,Force);
		else
			ShowTime(FALSE,Force);

		Force = FALSE;

			/* Show memory display. */

		if(DSeg -> Page == 1)
		{
			LONG BarLength;

				/* For single bitplane Workbench
				 * screen -> use fill pattern.
				 */

			static USHORT Checkers[8]=
			{
				0xAAAA,0x5555,
				0xAAAA,0x5555,
				0xAAAA,0x5555,
				0xAAAA,0x5555
			};

				/* How much memory is still available? */

			MemSize = AvailMem(MEMF_CHIP) + AvailMem(MEMF_FAST);

				/* How LONG will the bar be? */

			BarLength = ((Width - (EmptyWidth + FullWidth)) * MemSize) / MaxSize;

			if(NewKick)
			{
				SetAPen(DummyRPort,2);
				SetBPen(DummyRPort,3);
			}
			else
			{
				SetAPen(DummyRPort,1);
				SetBPen(DummyRPort,2);
			}

			Move(DummyRPort,0,Window -> IFont -> tf_Baseline);

			Text(DummyRPort,"E",1);

			Move(DummyRPort,Width - FullWidth,Window -> IFont -> tf_Baseline);

			Text(DummyRPort,"F",1);

				/* Draw the full part. */

			if(Window -> RPort -> BitMap -> Depth < 2)
				SetAfPt(DummyRPort,&Checkers[0],1);

			if(NewKick)
				SetAPen(DummyRPort,2);
			else
				SetAPen(DummyRPort,3);

			RectFill(DummyRPort,EmptyWidth,0,Width - FullWidth - BarLength,Height - 1);

			if(Window -> RPort -> BitMap -> Depth < 2)
				SetAfPt(DummyRPort,NULL,0);

				/* Add the empty part. */

			if(NewKick)
				SetAPen(DummyRPort,3);
			else
				SetAPen(DummyRPort,2);

			RectFill(DummyRPort,Width - FullWidth - BarLength,0,Width - (FullWidth + 1),Height - 1);
		}

			/* Numeric memory display. */

		if(DSeg -> Page == 2)
		{
			UBYTE TempBuff[30];

			Format(TempBuff,"C: %07ld  F: %07ld",AvailMem(MEMF_CHIP),AvailMem(MEMF_FAST));
			PrintIt(TempBuff);
		}

			/* Show online timer? */

		if(DSeg -> Page == 3)
		{
			UBYTE TempBuff[50];

			Format(TempBuff,"OFFLINE %02ld:%02ld:%02ld",OnlineHours,OnlineMinutes,OnlineSeconds);

			if(DSeg -> Online)
			{
				if(Blink ^= TRUE)
				{
					TempBuff[0] = ' ';
					TempBuff[1] = 'O';
					TempBuff[2] = 'N';
				}
				else
				{
					TempBuff[0] = ' ';
					TempBuff[1] = ' ';
					TempBuff[2] = ' ';
				}
			}

			PrintIt(TempBuff);
		}

			/* Show the countdown. */

		if(DSeg -> Page == 4)
		{
			UBYTE TempBuff[50];

			if(DSeg -> Countdown > 0)
			{
				Format(TempBuff,"Countdown %ld         ",-DSeg -> Countdown);
				TempBuff[22] = 0;

				PrintIt(TempBuff);
			}
			else
				DSeg -> Page = 0;
		}

			/* Transfer the image portion. */

		ClipBlit(DummyRPort,0,0,Window -> WScreen -> BarLayer -> rp,LeftEdge,1,Width,Height,0xC0);
	}
}
