/*
 *  CALC.C    Provides a calculator that opens on the active screen when
 *            you press a specific key sequence.  Otherwise, the program
 *            waits quitely in the background.
 *
 *              Copyright 1989 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "cLoader.h"

extern struct IntuitionBase *IntuitionBase;
extern struct GfxBase *GfxBase;

static char *program   = PROGRAM;
static char *copyright = COPYRIGHT;

static char *handler   = HANDLERCODE;       /* The name of the handler file */
#define HANDLER          &(handler[2])      /* Handler without the L: */

static struct HandlerData *cHandlerData;    /* data shared with handler */
static long Segment;                        /* The loaded handler segment */
static struct Process *HandlerTask;         /* The process created */

static struct MsgPort *InputPort;           /* To talk to Input.Device */
static struct IOStdReq *InputBlock;         /* IO block for Input.Device */
static int    InputDevice;                  /* Is Input.Device open? */
static int    VectorsSet;                   /* TRUE after SetFunction */
static int    ProcStarted;                  /* TRUE acter CreateProc */

static UWORD  KeyCode = 0x50;               /* the keycode to activate Calc */
static UWORD  Qualifiers = 0;               /*   and required qualifiers */


struct KeywordDef                           /* keywords and ther qualifiers */
{
   char *Name;
   UWORD Qualifier;
};

static struct KeywordDef Keyword[] =
{
   {"LSHIFT",   IEQUALIFIER_LSHIFT},
   {"RSHIFT",   IEQUALIFIER_RSHIFT},
   {"SHIFT",    IEQUALIFIER_LSHIFT},
   {"CAPSLOCK", IEQUALIFIER_CAPSLOCK},
   {"CONTROL",  IEQUALIFIER_CONTROL},
   {"LALT",     IEQUALIFIER_LALT},
   {"RALT",     IEQUALIFIER_RALT},
   {"ALT",      IEQUALIFIER_LALT},
   {"LAMIGA",   IEQUALIFIER_LCOMMAND},
   {"RAMIGA",   IEQUALIFIER_RCOMMAND},
   {"AMIGA",    IEQUALIFIER_LCOMMAND},
   {"LCOMMAND", IEQUALIFIER_LCOMMAND},
   {"RCOMMAND", IEQUALIFIER_RCOMMAND},
   {NULL}
};


/*
 *  DoExit()
 *
 *  General purpose error-exit routine.  Print an error message if one was
 *  supplied (it can have up to three parameters), and then clean up any
 *  memory, libraries, etc. that need to be handled before exiting.
 */

static void DoExit(s,x1,x2,x3)
char *s, *x1, *x2, *x3;
{
   long status = EXIT_OK;
   ULONG signals;
   
   if (s != NULL)
   {
      printf(s,x1,x2,x3);
      printf("\n");
      status = EXIT_ERROR;
   }
   if (VectorsSet)      UnSetVectors();
   if (InputDevice)     CloseDevice(InputBlock);
   if (InputBlock)      DeleteStdIO(InputBlock);
   if (InputPort)       DeletePort(InputPort);
   if (ProcStarted)
   {
      Signal(HandlerTask,ENDSIGNAL);
      signals = Wait(ENDSIGNAL | SIGBREAKF_CTRL_C);
      if (signals & SIGBREAKF_CTRL_C) printf("CTRL-C detected!!!\n");
   }
   if (Segment)                 UnLoadSeg(Segment);
   if (IntuitionBase)           CloseLibrary(IntuitionBase);
   if (GfxBase)                 CloseLibrary(GfxBase);
   exit(status);
}


/*
 *  CheckLibOpen()
 *
 *  Call OpenLibrary() for the specified library, and check that the 
 *  open succeeded.
 */

static 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",name);
}


/*
 *  CheckArguments()
 *
 *  For each command-line argument:
 *    Look through the keyword list for the named argument.
 *    If no match found, then
 *      Check to see if the argument is an F-key name ('F1','F2', etc).
 *      If so, then set the KeyCode accordingly,
 *      Otherwise, try to read a keycode number.
 *        If unsuccessful, then error.
 *    Otherwise (a keyword was found)
 *      so add the requested qualifier to the required qualifier list.
 */

static void CheckArguments(argc,argv)
int argc;
char **argv;
{
   short i;
   int NotFound;
   long Code;

   while (--argc)
   {
      argv++;
      for (i=0, NotFound=TRUE; Keyword[i].Name && NotFound; i++)
         NotFound = stricmp(*argv,Keyword[i].Name);
      if (NotFound)
      {
         if (((*argv)[0] == 'F' || (*argv)[0] == 'f') &&
            (((*argv)[1] >= '1' && (*argv)[1] <= '9' && (*argv)[2] == 0) ||
             ((*argv)[1] == '1' && (*argv)[2] == '0' && (*argv)[3] == 0)))
         {
            if ((*argv)[2] == 0)
               KeyCode = 0x50 + (*argv)[1] - '1';
              else
               KeyCode = 0x59;
         } else {
            if (sscanf(*argv,"%x",&Code) != 1)
               DoExit("Unrecognized keyword '%s'",*argv);
            KeyCode = Code;
         }
      } else {
         Qualifiers ^= Keyword[i-1].Qualifier;
      }
   }
}


/*
 *  LoadHandler()
 *
 *  Try to LoadSeg the handler from the current directory, and if it is not
 *  found, try the L: directory.  If neither can be loaded, exit with an
 *  error message.  Once the handler is loaded, create a process from the
 *  loaded segment (error if it could not be created) and set a flag so
 *  that we can clean up if other errors occur.
 */

static void LoadHandler()
{
   struct Process *theProcess;

   if ((Segment = LoadSeg(HANDLER)) == NULL)
      if ((Segment = LoadSeg(handler)) == NULL)
        DoExit("Can't load %s",handler);
   theProcess = CreateProc(HANDLERTASKNAME,0L,Segment,2048L);
   if (theProcess == NULL) DoExit("Can't Create Handler Process");
   ProcStarted = TRUE;
}


/*
 *  SetupHandler()
 *
 *  Look for the handler task, and error if it can't be found.  Get a message
 *  port and a new StartupMessage structure to send the the Handler.  Put
 *  the loader version number in the StartupMessage so the Handler can check
 *  whether it is OK (if not, it will return a NULL HandlerData pointer).
 *  Send the message to the Handler, and wait for a reply.  When it is
 *  returned, get the HandlerData pointer, and free what we allocated.  
 *  If the HandlerData is NULL, then there was a version mismatch reported 
 *  by the Handler, otherwise, check the handler version to make sure we know
 *  how to set it up.  Finally, set thePort the the Handler Port value.
 */

static void SetupHandler(thePort)
struct MsgPort **thePort;
{
   struct StartupMessage *sMessage;
   struct MsgPort *rPort;
   ULONG signals;
   ULONG rSig;

   HandlerTask = FindTask(HANDLERTASKNAME);
   if (HandlerTask == NULL) DoExit("Can't Find Handler Task");
   NEWPORT(rPort);
   if (NEWSTARTUP(sMessage))
   {
      rSig = ONE << rPort->mp_SigBit;
      sMessage->sm_Message.mn_ReplyPort = rPort;
      sMessage->sm_HandlerData = NULL;
      sMessage->sm_ParentTask = (struct Task *)FindTask(NULL);
      sMessage->sm_LoadVers = LOADVERS;
      PutMsg(&(HandlerTask->pr_MsgPort),sMessage);
      signals = Wait(rSig | SIGBREAKF_CTRL_C);
      if (signals & SIGBREAKF_CTRL_C) DoExit("CTRL-C Detected!");
      GETSTARTUP(rPort); cHandlerData = sMessage->sm_HandlerData;
      FREESTARTUP(sMessage);
      DeletePort(rPort);
      if (cHandlerData == NULL) DoExit("%s reports a version mismatch",HANDLER);
      if (var(MajVers) < MINHMAJVERS ||
         (var(MajVers) == MINHMAJVERS && var(MinVers) < MINHMINVERS))
            DoExit("Version mismatch with %s",HANDLER);
      *thePort = &(cHandlerData->HandlerPort);
   } else {
      DeletePort(rPort);
      DoExit("Can't Get Memory for Startup Message");
   }
}

/*
 *  StartHandler()
 *
 *  Send the Handler the start signal so that it know everything is
 *  set up for it.  Clear the old ParentTask pointer, since the task
 *  that eventually removes the process may not be the same as the one
 *  that started it.
 */

void StartHandler()
{
   Signal(HandlerTask,STARTSIGNAL);

   Forbid();
   VAR(ParentTask) = NULL;
   Permit();
}


/*
 *  StopHandler()
 *
 *  Send the Handler the stop signal, and wait for a reply (the Handler
 *  may need to clean things up before we remove it).  Be sure that the
 *  ParentTask points to us, and not to the original loader task, which 
 *  may not be the same.
 */

void StopHandler()
{
   ULONG signals;

   Forbid();
   VAR(ParentTask) = (struct Task *)FindTask(NULL);
   Permit();

   Signal(HandlerTask,ENDSIGNAL);
   signals = Wait(ENDSIGNAL | SIGBREAKF_CTRL_C);
   if (signals & SIGBREAKF_CTRL_C) DoExit("CTRL-C detected!!");
}


/*
 *  TellInputDevice()
 *
 *  Create a port and I/O block, then 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;
   extern struct MsgPort *CreatePort();
   extern struct IOStdReq *CreateStdIO();

   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) var(Handler_Interrupt);
   if (status = DoIO(InputBlock)) DoExit("Error from DoIO:  %ld",status);

   CloseDevice(InputBlock); InputDevice = FALSE;
   DeleteStdIO(InputBlock); InputBlock = NULL;
   DeletePort(InputPort);   InputPort = NULL;
}


/*
 *  SetVectors()
 *
 *  Set the Intuition library vectors for the routines specified by the
 *   handler.  Save the old routine pointers for later replacement.
 */

void SetVectors()
{
   if (var(aCloseScreen) && var(OldCloseScreen))
   {
      VAR(OldCloseScreen) =
         SetFunction(IntuitionBase,&LVOCloseScreen,var(aCloseScreen));
      VectorsSet = TRUE;
   }
}


/*
 *  UnSetVectors()
 *
 *  Replace the old Intuition library vectors, but make sure that no one
 *  else has changed them behind our back.  If they are not the same as
 *  what we set them to originally, then put back the ones that we found,
 *  and return an error status.
 */

int UnSetVectors()
{
   long NewCloseScreen;
   int status = TRUE;

   if (var(aCloseScreen) && var(OldCloseScreen))
   {
      Forbid();
      NewCloseScreen =
         SetFunction(IntuitionBase,&LVOCloseScreen,VAR(OldCloseScreen));
      if (NewCloseScreen != (long) var(aCloseScreen))
      {
         SetFunction(IntuitionBase,&LVOCloseScreen,NewCloseScreen);
         status = FALSE;
      }
      Permit();
   }
   return(status);
}


/*
 *  SetVariables()
 *
 *  The HandlerData structure is used to allow the loading program to
 *  set up variables needed by the handler (like Intuitionbase, etc.).  This
 *  keeps the handler code to a minimum.  The loader retains pointers to the
 *  linked lists, in case it needs to free memory on behalf of the handler.
 */

void SetVariables(thePort)
struct MsgPort *thePort;
{
   VAR(IntuitionBase) = IntuitionBase;
   VAR(GfxBase) = GfxBase;
   var(Segment) = Segment;
   VAR(KeyCode) = KeyCode;
   VAR(Qualifiers) = Qualifiers;
}


/*
 *  GetVariables()
 *
 *  Look up the values stored in the HandlerData structure.  The 
 *  Intuition library already was opened, and we will need to close it.
 *  Similarly for the Graphics library.
 */

void GetVariables(thePort)
struct MsgPort *thePort;
{
   cHandlerData = (struct HandlerData *)thePort;
   IntuitionBase = VAR(IntuitionBase);
   GfxBase = VAR(GfxBase);
}


/*
 *  Main()
 *
 *  Look for the Handler Process.
 *  If the process does not exist, then the Handler is not active, so:
 *    Handle the arguments (if any).
 *    Open libraries.
 *    Load the handler code and create the process.
 *    Send the startup message to the process and get the HandlerData.
 *    Set the variables needed by the handler.
 *    Set the library vectors for the handler routines.
 *    Add the input handler into the Input Device chain.
 *    Add the port (supplied by the handler) into the system list so we
 *      can find it later.
 *    Signal the process that all is OK.
 *    Notify the user that all is ready.
 *  else (the process already exists, so the Handler already is active)
 *    Get the pointer to the HandlerData structure from the port,
 *      and get any variables we need from the structure.
 *    Check that the loader versions are compatible.
 *    Remove the input handler from the Input Device chain
 *    Signal the Handler to stop, and wait for reply.
 *    Try to remove the SetFunction calls.
 *    If successfull, then
 *      Remove the port from the system list.
 *      Signal Handler to end and wait for reply.
 *      Unload the handler segment list.
 *      Notify the user that the Handler is deactivated.
 *      Close libraries.
 *    else (we could not replace the functions)
 *      Put back the input handler.
 *      Signal the Handler to continue.
 *      Inform the user that the Handler can not be removed.
 */
 
void main(argc,argv)
int argc;
char **argv;
{
   struct MsgPort *NamedPort;

   HandlerTask = FindTask(HANDLERTASKNAME);
   if (HandlerTask == NULL)
   {
      if (argc > 1) CheckArguments(argc,argv);
      CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
      CheckLibOpen(&GfxBase,"graphics.library",GRAPHICS_REV);
      LoadHandler();
      SetupHandler(&NamedPort);
      SetVariables(NamedPort);
      SetVectors(NamedPort);
      TellInputDevice(IND_ADDHANDLER);
      AddPort(NamedPort);
      StartHandler();
      printf("%s v%d.%d.%d Installed\n",program,
         var(MajVers),var(MinVers),LOADVERS);
   } else {
      NamedPort = FindPort(PORTNAME);
      if (NamedPort == NULL) DoExit("Can't find port '%s'",PORTNAME);
      GetVariables(NamedPort);
      if (var(MinLoadVers) > LOADVERS || var(MajVers) < MINHMAJVERS ||
         (var(MajVers) == MINHMAJVERS && var(MinVers) < MINHMINVERS))
      {
         printf("Loader version mismatch\n");
         printf("%s not removed\n",program);
      } else {
         TellInputDevice(IND_REMHANDLER);
         StopHandler();
         if (UnSetVectors(NamedPort))
         {
            RemPort(NamedPort);
            StopHandler();
            UnLoadSeg(var(Segment));
            printf("%s removed\n",program);
            CloseLibrary(IntuitionBase);
            CloseLibrary(GfxBase);
         } else {
            TellInputDevice(IND_ADDHANDLER);
            StartHandler();
            printf("SetFunction vectors have been changed!\n");
            printf("Cannot remove %s\n",program);
         }
      }
   }
}
