/****************************************************************************
*
*	KeyMacro.Handler.c ----	KeyMacro handler.
*
*	Author ----------------	Olaf Barthel, MXM
*				Brabeckstrasse 35
*				D-3000 Hannover 71
*
*	KeyMacro  ©  Copyright  1990  by  MXM;  Executable  program,
*	documentation  and  source  code are shareware.  If you like
*	this  program  a  small donation will entitle you to receive
*	updates and new programs from MXM.
*
****************************************************************************/

	/* Function prototypes. */

VOID			FreeString(BPTR);
BPTR			CreateBSTR(char *);
BPTR			CopyPath(VOID);
VOID			FakeCLI(VOID);
VOID			ClearPath(BPTR);
VOID			StopFakery(VOID);
VOID *			DeleteMacroMsg(struct MacroMessage *);
VOID *			SendMacroMsg(struct MacroMessage *,struct MsgPort *);
VOID			Executor(VOID);
struct InputEvent *	EventHandler(struct InputEvent *);
struct MacroKey *	FindMacroKey(LONG,LONG);
LONG			CreateHandler(VOID);
VOID			DeleteHandler(VOID);

LONG			_main(VOID);

	/* The magic stuff. */

#pragma regcall(EventHandler(a0))

	/* Shared library identifiers. */

struct MXMBase		*MXMBase;
struct IntuitionBase	*IntuitionBase;
struct Library		*ConsoleDevice;
extern struct ExecBase	*SysBase;

	/* Global handshake data. */

struct MSeg		*MSeg;
struct KeyEquivalent	*EquList;

	/* Process<->Process communication data. */

struct Process		*ExecuteProc;
struct MsgPort		*ExecutePort;

	/* Input device data. */

struct MsgPort		*InputDevPort;
struct IOStdReq		*InputRequestBlock;
struct Interrupt	*InputHandler;
struct InputEvent	*FakeInputEvent;

	/* _main():
	 *
	 *	This is the entry point to the handler process.
	 */

LONG
_main()
{
	struct Process		*ThatsMe;
	ULONG			 SignalSet;
	struct MacroMessage	*MacroMsg;
	LONG			 SigBit;
	LONG			 i;

		/* Do I know myself? */

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

		/* Don't let anybody call us from CLI. */

	if(ThatsMe -> pr_CLI)
		goto Quit;

		/* Can we find the global MsgPort? */

	if(!(MSeg = (struct MSeg *)FindPort(PORTNAME)))
	{
		Forbid();

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

		goto Quit;
	}

		/* This older revision probably doesn't support
		 * some newer structure tags -> exit.
		 */

	if(MSeg -> Revision < REVISION)
	{
		Forbid();

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

		goto Quit;
	}

		/* The MsgPort is already owned by someone
		 * else.
		 */

	if(MSeg -> Port . mp_Flags & PA_SIGNAL)
	{
		Forbid();

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

		goto Quit;
	}

		/* Open mxm.library. */

	if(!(MXMBase = (struct MXMBase *)OpenLibrary("mxm.library",34)))
	{
		Forbid();

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

		goto Quit;
	}

		/* Initialize the input.device handler. */

	if(!CreateHandler())
	{
		DeleteHandler();

		CloseLibrary((struct Library *)MXMBase);

		Forbid();

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

		goto Quit;
	}

		/* Extract shared library identifiers. */

	IntuitionBase = (struct IntuitionBase *)MXMBase -> IntuitionBase;
	ConsoleDevice = (struct Library *)MXMBase -> ConsoleDevice;

		/* Allocate a fake InputEvent structure. */

	if(!(FakeInputEvent = (struct InputEvent *)AllocRem(sizeof(struct InputEvent),MEMF_PUBLIC)))
	{
		DeleteHandler();
		CloseLibrary((struct Library *)MXMBase);

		Forbid();

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

		goto Quit;
	}

		/* Allocate a signal bit. */

	if((SigBit = AllocSignal(-1)) == -1)
	{
		FreeRem(FakeInputEvent);

		DeleteHandler();

		CloseLibrary((struct Library *)MXMBase);

		Forbid();

		MSeg -> Child = NULL;
		Signal(MSeg -> Father,MSeg -> RingBack);

		goto Quit;
	}

		/* Start the executing process. */

	MSeg -> Child = (struct Task *)ThatsMe;

	if(!(ExecuteProc = (struct Process *)CreateFuncProc("KeyMacro.exec",10,Executor,4000)))
	{
		FreeSignal(SigBit);

		FreeRem(FakeInputEvent);

		DeleteHandler();

		CloseLibrary((struct Library *)MXMBase);

		Forbid();

		MSeg -> Child = NULL;
		Signal(MSeg -> Father,MSeg -> RingBack);

		goto Quit;
	}

		/* Wait for handshake signal. */

	Wait(SIG_SHAKE);

		/* Process creation failed. */

	if(!ExecuteProc)
	{
		FreeSignal(SigBit);

		FreeRem(FakeInputEvent);

		DeleteHandler();
		CloseLibrary((struct Library *)MXMBase);

		Forbid();

		MSeg -> Child = NULL;
		Signal(MSeg -> Father,MSeg -> RingBack);

		goto Quit;
	}

		/* Now we are truly running. */

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

	MSeg -> Father = NULL;

		/* Re-init the MsgPort flags. */

	MSeg -> Port . mp_Flags		= PA_SIGNAL;
	MSeg -> Port . mp_SigBit	= SigBit;
	MSeg -> Port . mp_SigTask	= MSeg -> Child;

		/* Wait until somebody kicks us out. */

	FOREVER
	{
		SignalSet = Wait(SIG_CLOSE | SIG_PORT);

			/* We are to shut down. */

		if(SignalSet & SIG_CLOSE)
		{
			FreeSignal(SigBit);

			FreeRem(FakeInputEvent);

			DeleteHandler();

			if(ExecuteProc)
			{
				Signal((struct Task *)ExecuteProc,SIG_CLOSE);

				Wait(SIG_SHAKE);
			}

			CloseLibrary((struct Library *)MXMBase);

			Forbid();

			Signal(MSeg -> Father,SIG_CLOSE);

			goto Quit;
		}

			/* A message arrived at our home port. */

		if(SignalSet & SIG_PORT)
		{
				/* Walk through the list of messages. */

			while(MacroMsg = (struct MacroMessage *)GetMsg(&MSeg -> Port))
			{
					/* Execute a keyboard macro. */

				if(MacroMsg -> mm_Type == MM_INPUT)
				{
					struct MacroKey *TempMacroKey = MacroMsg -> mm_MacroKey;

					if(TempMacroKey)
					{
							/* Let the execute process run the command. */

						if(TempMacroKey -> mk_Type == MK_COMMAND)
						{
							struct MacroMessage CommandMsg;

							CommandMsg . mm_Type		= MM_EXECUTE;
							CommandMsg . mm_FileName	= TempMacroKey -> mk_String;
							CommandMsg . mm_WindowName	= TempMacroKey -> mk_Window;

							SendMacroMsg(&CommandMsg,ExecutePort);
						}

							/* Build a keyboard macro. */

						if(TempMacroKey -> mk_Type == MK_WORD)
						{
							InputRequestBlock -> io_Command	= IND_WRITEEVENT;
							InputRequestBlock -> io_Data	= (APTR)FakeInputEvent;

							memset(FakeInputEvent,0,sizeof(struct InputEvent));

							FakeInputEvent -> ie_Class = IECLASS_RAWKEY;

							for(i = 0 ; i < strlen(TempMacroKey -> mk_String) ; i++)
								if(InvertKey(TempMacroKey -> mk_String[i],FakeInputEvent,IK_USEIKM,NULL))
									DoIO(InputRequestBlock);
						}
					}
				}

					/* This is a request to update the
					 * macro keys.
					 */

				if(MacroMsg -> mm_Type == MM_UPDATE)
				{
					if(MSeg -> MacroList)
					{
						for(i = 0 ; i < MSeg -> NumMacros ; i++)
						{
							if(MSeg -> MacroList[i] . mk_Type == MK_UNUSED)
								continue;

							if(MSeg -> MacroList[i] . mk_String)
								FreeRem(MSeg -> MacroList[i] . mk_String);

							if(MSeg -> MacroList[i] . mk_Window)
								FreeRem(MSeg -> MacroList[i] . mk_Window);
						}

						FreeRem(MSeg -> MacroList);
					}

					MSeg -> NumMacros = MacroMsg -> mm_NumMacros;
					MSeg -> MacroList = MacroMsg -> mm_MacroList;
				}

					/* Remove the message. */

				DeleteMacroMsg(MacroMsg);
			}
		}
	}

Quit:	;
}

	/* FreeString(Byte):
	 *
	 *	Frees the memory occupied by the contents of a BSTR.
	 */

VOID
FreeString(BPTR Byte)
{
	LONG *Ptr = (LONG *)BADDR(Byte);

	FreeMem(Ptr - 1,Ptr[-1]);
}

	/* CreateBSTR(s):
	 *
	 *	Allocates enough memory to hold the contents of
	 *	a given string and makes it a BSTR.
	 */

BPTR
CreateBSTR(char *s)
{
	LONG  Length = strlen(s);
	LONG  BlockLength = (Length + 8) & ~3;
	char *Byte;

	if(!(Byte = (char *)AllocMem(BlockLength,MEMF_PUBLIC | MEMF_CLEAR)))
		return(NULL);

	*(LONG *)Byte = BlockLength;

	Byte[4] = Length;
	strncpy(Byte + 5,s,Length);

	return((LONG)(Byte + 4) >> 2);
}

	/* CopyPath():
	 *
	 *	Builds a fake pathlist inherited from any valid
	 *	CLI process or Workbench.
	 */

BPTR
CopyPath()
{
	struct Process			*Father;
	struct CommandLineInterface	*CLI;
	BPTR				*Next1,*Next2,*Last,NewPath = NULL;

	Last = &NewPath;

		/* If using ARP this will also give us a valid
		 * pathlist.
		 */

	if(!(Father = (struct Process *)FindTask("Workbench")))
		if(!(Father = (struct Process *)FindTask("ARP Shell Process")))
			if(!(Father = (struct Process *)FindTask("New CLI")))
				if(!(Father = (struct Process *)FindTask("Initial CLI")))
					return(NULL);

	if(!(CLI = (struct CommandLineInterface *)BADDR(Father -> pr_CLI)))
		return(NULL);

	for(Next1 = (BPTR *)BADDR(CLI -> cli_CommandDir) ; Next1 ; Next1 = (BPTR *)BADDR(*Next1))
	{
		if(!(Next2 = (BPTR *)AllocMem(2 * sizeof(BPTR),MEMF_PUBLIC | MEMF_CLEAR)))
			break;

		*Last = (LONG)Next2 >> 2;
		Last = Next2;

		Next2[1] = (BPTR)DupLock(Next1[1]);
		Next2[0] = NULL;
	}

	return(NewPath);
}

	/* FakeCLI():
	 *
	 *	Creates a fake CLI structure for our process. This
	 *	includes pathlist, currentdir, prompt and stack.
	 */

VOID
FakeCLI()
{
	struct CommandLineInterface	*CLI;
	struct Process			*MyProcess = (struct Process *)SysBase -> ThisTask;

	if(!(CLI = (struct CommandLineInterface *)AllocMem(sizeof(struct CommandLineInterface),MEMF_PUBLIC | MEMF_CLEAR)))
		return;

	MyProcess -> pr_CLI = (LONG)CLI >> 2;

	CLI -> cli_SetName	= CreateBSTR("SYS:");
	CLI -> cli_Prompt	= CreateBSTR("%N> ");
	CLI -> cli_DefaultStack	= 4000;

	CurrentDir(Lock("SYS:",ACCESS_READ));

	CLI -> cli_CommandDir	= CopyPath();
}

	/* ClearPath(InitPath):
	 *
	 *	Frees the contents of our fake pathlist.
	 */

VOID
ClearPath(BPTR InitPath)
{
	BPTR *Next,*Path;

	for(Path = (BPTR *)BADDR(InitPath) ; Path ; Path = Next)
	{
		Next = (BPTR *)BADDR(Path[0]);

		if(Path[1])
			UnLock(Path[1]);

		FreeMem(Path,2 * sizeof(BPTR));
	}
}

	/* StopFakery():
	 *
	 *	Removes the contents of our fake CLI structure.
	 */

VOID
StopFakery()
{
	BPTR				 MyCD = (BPTR)CurrentDir(NULL);
	struct Process			*MyProcess = (struct Process *)SysBase -> ThisTask;
	struct CommandLineInterface	*CLI = (struct CommandLineInterface *)BADDR(MyProcess -> pr_CLI);

	if(!CLI)
		return;

	if(MyCD)
		UnLock(MyCD);

	FreeString(CLI -> cli_SetName);
	FreeString(CLI -> cli_Prompt);

	ClearPath(CLI -> cli_CommandDir);

	MyProcess -> pr_CLI = NULL;

	FreeMem(CLI,sizeof(struct CommandLineInterface));
}

	/* DeleteMacroMsg(scm_Msg):
	 *
	 *	Remove a message from memory.
	 */

VOID *
DeleteMacroMsg(struct MacroMessage *scm_Msg)
{
	if(scm_Msg && scm_Msg -> mm_Message . mn_Node . ln_Name == (char *)scm_Msg)
		FreeRem(scm_Msg);

	return(NULL);
}

	/* SendMacroMsg(scm_Msg,scm_Port):
	 *
	 *	Post a cloned macro message to a MsgPort.
	 */

VOID *
SendMacroMsg(struct MacroMessage *scm_Msg,struct MsgPort *scm_Port)
{
	struct MacroMessage *scm_TempMsg = (struct MacroMessage *)AllocRem(sizeof(struct MacroMessage),MEMF_PUBLIC | MEMF_CLEAR);

	if(scm_TempMsg)
	{
		CopyMem(scm_Msg,scm_TempMsg,sizeof(struct MacroMessage));

		scm_TempMsg -> mm_Message . mn_Node . ln_Name	= (char *)scm_TempMsg;
		scm_TempMsg -> mm_Message . mn_ReplyPort	= NULL;
		scm_TempMsg -> mm_Message . mn_Length		= sizeof(struct MacroMessage);

		PutMsg(scm_Port,(struct Message *)scm_TempMsg);
	}

	return((VOID *)scm_TempMsg);
}

	/* Executor():
	 *
	 *	This is the dummy process to execute programs.
	 */

VOID
Executor()
{
	ULONG			 SignalSet;
	BPTR			 NIL;
	struct MacroMessage	*ExecuteMsg;
	struct Window		*TheWindow;
	char			 TempLine[300];

	geta4();

		/* Try to allocate a port (we can't use our builtin
		 * DOS port since we are actually calling DOS
		 * routines which may mix up the messages coming
		 * in).
		 */

	if(!(ExecutePort = (struct MsgPort *)CreatePort(NULL,0)))
	{
		Forbid();

		ExecuteProc = NULL;

		Signal(MSeg -> Child,SIG_SHAKE);

		goto Quit;
	}

		/* Open the NULL-Handler. */

	if(!(NIL = Open("NULL:",MODE_NEWFILE)))
	{
		Forbid();

		ExecuteProc = NULL;

		Signal(MSeg -> Child,SIG_SHAKE);
		DeletePort(ExecutePort);

		goto Quit;
	}

		/* Pretend to be a CLI. */

	FakeCLI();
	{
		struct Process *ThatsMe = (struct Process *)SysBase -> ThisTask;

			/* These are inherited from the father process,
			 * we had better cleared them out.
			 */

		ThatsMe -> pr_ConsoleTask	= (APTR)DeviceProc("NULL:");
		ThatsMe -> pr_WindowPtr		= (APTR)-1;

			/* This path leads nowhere. */

		ThatsMe -> pr_CIS		= NIL;
		ThatsMe -> pr_COS		= NIL;
	}

		/* We're on the scene now. */

	Signal(MSeg -> Child,SIG_SHAKE);

	FOREVER
	{
		SignalSet = Wait(SIG_CLOSE | (1 << ExecutePort -> mp_SigBit));

			/* Shut down? */

		if(SignalSet & SIG_CLOSE)
		{
			StopFakery();
			Close(NIL);
			DeletePort(ExecutePort);

			ExecuteProc = NULL;

			Forbid();

			Signal(MSeg -> Child,SIG_SHAKE);

			goto Quit;
		}

			/* Execute a command? */

		while(ExecuteMsg = (struct MacroMessage *)GetMsg(ExecutePort))
		{
			TheWindow = NULL;

				/* Try to find a matching window title. */

			if(ExecuteMsg -> mm_WindowName)
			{
				ULONG IntuiLock;

				struct Screen *ExScreen;
				struct Window *ExWindow;

				IntuiLock = LockIBase(NULL);

				ExScreen = IntuitionBase -> FirstScreen;

				do
				{
					ExWindow = ExScreen -> FirstWindow;

					do
					{
						if(!UStrCmp(ExecuteMsg -> mm_WindowName,ExWindow -> Title))
						{
							UnlockIBase(IntuiLock);

							TheWindow = ExWindow;

							goto SkipLoop;
						}
					}
					while(ExWindow = ExWindow -> NextWindow);
				}
				while(ExScreen = ExScreen -> NextScreen);

				UnlockIBase(IntuiLock);
			}

				/* No chance, execute the command. */

			strcpy(TempLine,"C:Run <NULL: >NULL: ");
			strcat(TempLine,ExecuteMsg -> mm_FileName);

			Execute(TempLine,NULL,NIL);

SkipLoop:		DeleteMacroMsg(ExecuteMsg);

				/* Found a window? Bring it to the front. */

			if(TheWindow)
			{
				WindowToFront(TheWindow);
				ScreenToFront(TheWindow -> WScreen);
				ActivateWindow(TheWindow);
			}
		}
	}

		/* Finished, fall through. */

Quit:	;
}

	/* FindMacroKey(Code,Qualifier):
	 *
	 *	Find a macro key entry in the linked list of
	 *	macro key structures.
	 */

struct MacroKey *
FindMacroKey(LONG Code,LONG Qualifier)
{
	LONG i;

	if(!MSeg -> MacroList)
		return(NULL);

	for(i = 0 ; i < MSeg -> NumMacros ; i++)
	{
		if(MSeg -> MacroList[i] . mk_Type == MK_UNUSED)
			continue;

		if(MSeg -> MacroList[i] . mk_CommandKey == Code && (Qualifier & MSeg -> MacroList[i] . mk_CommandQualifier) == MSeg -> MacroList[i] . mk_CommandQualifier)
			return(&MSeg -> MacroList[i]);
	}

	return(NULL);
}

	/* EventHandler(Event):
	 *
	 *	The input event handler.
	 */

struct InputEvent *
EventHandler(struct InputEvent *Event)
{
	struct MacroKey *HandlerKey;

		/* This is an interrupt, let's start with the register
		 * saving.
		 */

	int_start();

	if(Event -> ie_Class == IECLASS_RAWKEY && !(Event -> ie_Code & IECODE_UP_PREFIX))
	{
		if(HandlerKey = (struct MacroKey *)FindMacroKey(Event -> ie_Code,Event -> ie_Qualifier))
		{
			struct MacroMessage HandlerMsg;

			HandlerMsg . mm_Type	= MM_INPUT;
			HandlerMsg . mm_MacroKey= HandlerKey;

			SendMacroMsg(&HandlerMsg,&MSeg -> Port);

			Event -> ie_Class = IECLASS_NULL;
		}
	}

		/* Restore the registers. */

	int_end();

	return(Event);
}

	/* CreateHandler():
	 *
	 *	Initialize the input event handler.
	 */

LONG
CreateHandler()
{
		/* The basic initialization. */

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

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

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

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

		/* Build the input handler. */

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

		/* Add the handler. */

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

	if(DoIO((struct IORequest *)InputRequestBlock))
		return(FALSE);

	return(TRUE);
}

	/* DeleteHandler():
	 *
	 *	Remove the input event handler.
	 */

VOID
DeleteHandler()
{
		/* Remove the input handler. */

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

			DoIO((struct IORequest *)InputRequestBlock);

			CloseDevice((struct IORequest *)InputRequestBlock);
		}

		DeleteStdIO(InputRequestBlock);
	}

		/* Free the last memory. */

	if(InputHandler)
		FreeRem(InputHandler);

	if(InputDevPort)
		DeletePort(InputDevPort);
}
