/*
 *  ClickUpFront.c  Brings a window to the front when you double-click
 *                  in it (unless specified qualifier keys are also pressed).
 *
 *             Copyright (c) 1987 by Davide P. Cervone
 *  You may use this code provided this copyright notice is left intact.
 */

#include <exec/types.h>
#include <libraries/dos.h>
#include <exec/io.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <devices/input.h>
#include <devices/inputevent.h>

static char *program  = "ClickUpFront";
static char *version  = "v1.0";
static char *date     = "July 1987";
static char *author   = "Copyright (c) 1987 by Davide P. Cervone";

static char *PortName = "ClickUpFrontPort";
static char *handler  = "L:Click-Handler";     /* the handler file */
#define HANDLER            &(handler[2])       /* without the "L:" */

#define STAYMASK      (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)


/*
 *  This is the structure that holds the handler information between calls
 *  to ClickUpFront.  We create a named, public message-port that points to
 *  an instance of this structure so that we can find this information
 *  when we are asked to remove the handler.  The PortName is stored here 
 *  (NamedPort->mp_Node.ln_Name uses this area for it's name).   The other 
 *  data are what we need in order to remove the handler and clean up properly.
 */

struct HandlerBlock
{
   char *PortName;                  /* name of the public, named message-port */
   struct Interrupt Handler;        /* the handler added to the handler chain */
   struct IntuitionBase *Ibase;     /* library base used by the handler */
   struct LayersBase *Lbase;        /* library base used by the handler */
   long Segment;                    /* pointer from LoadSeg() */
};
#define HANDLERINFOSIZE     sizeof(struct HandlerBlock)
#define NAMESIZE            (strlen(PortName)+1)

extern struct MsgPort *CreatePort();
extern struct IOStdReq *CreateStdIO();
extern struct MsgPort *FindPort(), *CreatePort();
extern APTR AllocMem();
extern long LoadSeg();

#define INTUITION_REV   0L
#define LAYERS_REV      0L

struct IntuitionBase  *IntuitionBase = NULL;
struct LayersBase     *LayersBase    = NULL;
extern struct SysBase *SysBase;

struct MsgPort *InputPort = NULL;     /* Port used to talk to Input.Device */
struct IOStdReq *InputBlock = NULL;   /* request block used with Input.Device */
LONG InputDevice = 0;                 /* flag whether Input.Device is open */
struct MsgPort *NamedPort = NULL;     /* holds info needed to remove handler */
struct HandlerBlock *HandlerInfo = NULL; /* holds info stored in NamedPort */


/*
 *  DoExit()
 *
 *  General purpose exit routine.  If 's' is not NULL, then print an
 *  error message with up to three parameters.  Free any memory, close
 *  any open device, delete any ports, close any libraries, etc.
 */

void DoExit(s,x1,x2,x3)
char *s, *x1, *x2, *x3;
{
   long status = RETURN_OK;
   
   if (s != NULL)
   {
      printf(s,x1,x2,x3);
      printf("\n");
      status = RETURN_ERROR;
   }
   if (InputDevice)   CloseDevice(InputBlock);
   if (InputBlock)    DeleteStdIO(InputBlock);
   if (InputPort)     DeletePort(InputPort);
   if (NamedPort)     DeletePort(NamedPort);
   if (HandlerInfo)
   {
      if (HandlerInfo->PortName) FreeMem(HandlerInfo->PortName,NAMESIZE);
      FreeMem(HandlerInfo,HANDLERINFOSIZE);
   }
   if (IntuitionBase) CloseLibrary(IntuitionBase);
   if (LayersBase)    CloseLibrary(LayersBase);
   exit(status);
}


/*
 *  CheckLibOpen()
 *
 *  General library open routine.  It opens a library and sets a pointer
 *  to it.  It checks that the library was openned successfully.
 */

void CheckLibOpen(lib,name,rev)
APTR *lib;
char *name;
int rev;
{
   extern APTR OpenLibrary();

   if ((*lib = OpenLibrary(name,(LONG)rev)) == NULL)
      DoExit("Can't open '%s'\n",name);
}


/*
 *  Macros that make memory allocation easier.
 */
#define NEW(s,var)      (var = (struct s *)New("var",sizeof(struct s)))
#define NEWCHAR(var,s)  (var = (char *)New("var",s))


/*
 *  New()
 *
 *  Allocate public memory of a given size and set it to all zeros.  If there
 *  is not enough memory, then exit with an error, otherwise return the
 *  pointer to the newly allocated memory.
 */
 
APTR New(name,size)
char *name;
int size;
{
   APTR ptr;
   
   if ((ptr = AllocMem(size,MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
      DoExit("Can't Get Memory for '%s'");
   return(ptr);
}


/*
 *  TellInputDevice()
 *
 *  Create a port and I/O block, and open the input device.  Set up the
 *  I/O block to add or remove the input handler, and send the request
 *  to the input device.  Finally, close the device and delete the
 *  I/O block and port.
 */
 
void TellInputDevice(function)
int function;
{
   long status;

   if ((InputPort = CreatePort(0,0)) == NULL) DoExit("Can't Create Port");
   if ((InputBlock = CreateStdIO(InputPort)) == NULL)
      DoExit("Can't Create Standard IO Block");
   InputDevice = (OpenDevice("input.device",0,InputBlock,0) == 0);
   if (InputDevice == FALSE) DoExit("Can't Open 'input.device'");
   
   InputBlock->io_Command = (long) function;
   InputBlock->io_Data    = (APTR) &(HandlerInfo->Handler);
   if (status = DoIO(InputBlock)) DoExit("Error from DoIO:  %ld",status);

   CloseDevice(InputBlock);
   DeleteStdIO(InputBlock);
   DeletePort(InputPort);
}


/*
 *  CreateHandler()
 *
 *  Open the libraries needed by the input handler and store their locations
 *  in the HandlerInfo structure (so we can close them later).  Try to 
 *  LoadSeg() the handler.  If it is not in the current directory, try the
 *  L: directory.  Exit with an error if the handler can't be found.
 *  Convert the segment pointer into a pointer to the Setup routine (the
 *  first routine in the handler executable).  Call Setup() and pass it
 *  the pointers to the libraries that it will need to use.  Setup() returns
 *  a pointer to the actual handler routine that should be added into the
 *  input handler chain.  Store this in the HandlerInfo structure so we
 *  can use it to remove the handler later.  Set the handler priority to
 *  51 so that it is ahead of Intuition.
 *
 *  Finally, add the handler in the chain and tell the user that the
 *  handler has been installed.
 */

void CreateHandler(argc,argv)
int argc;
char **argv;
{
   long (*Setup)();
   long flags = STAYMASK;

   CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
   CheckLibOpen(&LayersBase,"layers.library",LAYERS_REV);
   
   if (argc > 1) sscanf(argv[1],"%lx",&flags);

   HandlerInfo->Ibase = IntuitionBase;
   HandlerInfo->Lbase = LayersBase;
   if ((HandlerInfo->Segment = LoadSeg(HANDLER)) == NULL)
      if ((HandlerInfo->Segment = LoadSeg(handler)) == NULL)
         DoExit("Can't Load '%s'",handler);
   Setup = (long (*)()) ((HandlerInfo->Segment << 2) + 4);
   HandlerInfo->Handler.is_Code = 
      (void (*)()) ((*Setup)(IntuitionBase,LayersBase,SysBase,flags));
   HandlerInfo->Handler.is_Node.ln_Pri = 51;

   TellInputDevice(IND_ADDHANDLER);
   printf("%s %s (%s) Installed\n",program,version,date);
}


/*
 *  Delete Handler()
 *
 *  Retreive the library pointers fro mthe HandlerInfo structure, where
 *  we stored them when we originally installed the handler, then remove
 *  the handler from the input handler chain.  Tell the user that the
 *  handler is gone, and then close the libraries that are no longer needed.
 */

void DeleteHandler()
{
   IntuitionBase = HandlerInfo->Ibase;
   LayersBase    = HandlerInfo->Lbase;

   TellInputDevice(IND_REMHANDLER);
   UnLoadSeg(HandlerInfo->Segment);
   printf("%s Removed\n",program);
   
   CloseLibrary(IntuitionBase);
   CloseLibrary(LayersBase);
}


/*
 *  main()
 *
 *  Check if a message port with our name already exists.
 *  If not, then the handler is not already installed, so:
 *    Allocate a new HandlerInfo structure and initialize the port name.
 *    Create a public, named message-port (we will look for this the next
 *      time ClickUpFront is called).
 *    Make the message port point to the HandlerInfo structure so we
 *      can use it later when the user asks us to remove the handler.
 *      Note that the port is not actually used for putting and getting
 *      messages, so the Task field is never used, so we can take it for
 *      our own uses.
 *    Finally, add the input handler into the chain.
 *  Otherwise, the port exists, so ClickUpFront already is installed:
 *    Retreive the HandlerInfo poiner from the port, and remove the 
 *      handler from the input handler chain.
 *    Free the memory used by the HandlerInfo, and delete the message port.
 */

void main(argc,argv)
int argc;
char **argv;
{
   NamedPort = FindPort(PortName);
   if (NamedPort == NULL)
   {
      NEW(HandlerBlock,HandlerInfo);
      NEWCHAR(HandlerInfo->PortName,NAMESIZE);
      strcpy(HandlerInfo->PortName,PortName);
      if ((NamedPort = CreatePort(HandlerInfo->PortName,0)) == NULL)
         DoExit("Can't Create Message Port '%s'",PortName);
      NamedPort->mp_SigTask = (struct Task *)HandlerInfo;
      CreateHandler(argc,argv);
   } else {
      HandlerInfo = (struct HandlerBlock *)(NamedPort->mp_SigTask);
      DeleteHandler();
      FreeMem(HandlerInfo->PortName,NAMESIZE);
      FreeMem(HandlerInfo,HANDLERINFOSIZE);
      DeletePort(NamedPort);
   }
}
