/*
 *  ZOOM-DAEMON   A program that implements Zoom gadgets for all Intuition
 *                windows that are opened while it is running.
 *
 *              Copyright 1989 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "Zoom-Main.h"
#include "Zoom-Daemon.h"

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

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 Zoom_HandlerInfo *Zoom_HandlerData; /* data shared with handler */
static long Segment;                        /* The loaded handler segment */

static struct ExtGadget *FirstZoom;         /* The list of zoom gadgets */

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


/*
 *  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;
   
   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 (Segment)         UnLoadSeg(Segment);
   if (IntuitionBase)   CloseLibrary(IntuitionBase);
   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);
}


/*
 *  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, call the Setup routine
 *  in the handler code and pass the loader version number.  The handler will
 *  check the version for compatibility and returns NULL if there is a
 *  mismatch, or a pointer to the shared data if everything is OK.
 *  Check the handler version number, and then store the loader version
 *  and the segment list pointer for use in unloading the handler later.
 *  The MsgPort is the first item in the Zoom_Handler structure.  It is used
 *  to link the information into the system port list, where we can find it
 *  later.
 */

void LoadHandler(thePort)
struct MsgPort **thePort;
{
   struct Zoom_HandlerInfo *(*Setup)();
   
   if ((Segment = LoadSeg(HANDLER)) == NULL)
      if ((Segment = LoadSeg(handler)) == NULL)
        DoExit("Can't load %s",handler);
   Setup = (struct Zoom_HandlerInfo *(*)()) ((Segment << 2) + 4);
   
   Zoom_HandlerData = (*Setup)(LOADVERS);
   if (Zoom_HandlerData)
   {
      if (var(MajVers) < MINHMAJVERS ||
         (var(MajVers) == MINHMAJVERS && var(MinVers) < MINHMINVERS))
             DoExit("Version mismatch with %s",HANDLER);
      *thePort = &(Zoom_HandlerData->Zoom_Port);
   } else {
      DoExit("%s reports a version mismatch",HANDLER);
   }
   
   var(Segment)  = Segment;
}


/*
 *  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(Zoom_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()
{
   VAR(OldOpenWindow)  =
      SetFunction(IntuitionBase,&LVOOpenWindow,var(aOpenWindow));
   VAR(OldCloseWindow) =
      SetFunction(IntuitionBase,&LVOCloseWindow,var(aCloseWindow));
   VAR(OldAddGadget) =
      SetFunction(IntuitionBase,&LVOAddGadget,var(aAddGadget));
   VAR(OldAddGList) =
      SetFunction(IntuitionBase,&LVOAddGList,var(aAddGList));
   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 NewOpenWindow;
   long NewCloseWindow;
   long NewAddGadget;
   long NewAddGList;
   int status = TRUE;

   Forbid();
   NewOpenWindow  =
      SetFunction(IntuitionBase,&LVOOpenWindow,VAR(OldOpenWindow));
   NewCloseWindow =
      SetFunction(IntuitionBase,&LVOCloseWindow,VAR(OldCloseWindow));
   NewAddGadget =
      SetFunction(IntuitionBase,&LVOAddGadget,VAR(OldAddGadget));
   NewAddGList =
      SetFunction(IntuitionBase,&LVOAddGList,VAR(OldAddGList));
   if (NewOpenWindow  != (long) var(aOpenWindow)  ||
       NewCloseWindow != (long) var(aCloseWindow) ||
       NewAddGadget   != (long) var(aAddGadget)   ||
       NewAddGList    != (long) var(aAddGList))
   {
      SetFunction(IntuitionBase,&LVOOpenWindow,NewOpenWindow);
      SetFunction(IntuitionBase,&LVOCloseWindow,NewCloseWindow);
      SetFunction(IntuitionBase,&LVOAddGadget,NewAddGadget);
      SetFunction(IntuitionBase,&LVOAddGList,NewAddGList);
      status = FALSE;
   }
   Permit();
   return(status);
}


/*
 *  SetVariables()
 *
 *  The Zoom_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(SysBase) = SysBase;
   VAR(FirstZoom) = NULL;
}


/*
 *  GetVariables()
 *
 *  Look up the values stored in the Zoom_HandlerData structure.  The 
 *  Intuition library already was opened, and we will need to close it.
 *  The data in the linked lists may need to be freed.
 */

void GetVariables(thePort)
struct MsgPort *thePort;
{
   Zoom_HandlerData = (struct Zoom_HandlerInfo *)thePort;
   IntuitionBase = VAR(IntuitionBase);
   FirstZoom = VAR(FirstZoom);
}


/*
 *  SetupWindows()
 *
 *  Looks through the Intuition screen and windows lists and
 *  calls the Handler's SetupWindow routine to add the imagery to
 *  each pre-existing window.
 *
 *  All of this is done in Forbid() mode so that the list won't change 
 *  while we're looking.
 */

static void SetupWindows()
{
   struct Screen *theScreen;
   struct Window *theWindow;
   
   if (var(SetupWindow))
   {
      Forbid();
      theScreen = IntuitionBase->FirstScreen;
      while (theScreen)
      {
         theWindow = theScreen->FirstWindow;
         while (theWindow)
         {
            VAR(SetupWindow)(theWindow);
            theWindow = theWindow->NextWindow;
         }
         theScreen = theScreen->NextScreen;
      }
      Permit();
   }
}


/*
 *  FreeAllZoomGadgets();
 *
 *  Goes through the linked list of zoom gadgets and removes each
 *  from its associated window, refreshes the window, and frees
 *  the memory associated with the zoom gadget.
 */

void FreeAllZoomGadgets()
{
   struct ExtGadget *theGadget;

   while (FirstZoom)
   {
      theGadget = FirstZoom;
      FirstZoom = theGadget->Next;
      RemoveGadget(theGadget->Window,&(theGadget->Gadget));
      RefreshWindowFrame(theGadget->Window);
      FreeMem(theGadget,EXTGADGETSIZE);
   }
   FirstZoom = NULL;
}


/*
 *  Main()
 *
 *  Look for the Zoom-Daemon port.
 *  If the port does not exist, then Zoom-Daemon is not active, so:
 *    Open Intuition.
 *    Load the handler code and check its version.
 *    Add the port (supplied by the handler) into the system list so we
 *      can find it later.
 *    Set the variables needed by the handler.
 *    Set the Intuition Library vectors for the handler routines
 *    Add the input handler into the Input Device chain
 *    Retro-fit existing windows to include the Zoom Gadgets
 *    Notify the user that all is ready.
 *  else (the port already exists, so Zoom-Daemon alreay is active)
 *    Get the pointer to the Zoom_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
 *    Try to remove the SetFunction calls.
 *    If successfull, then
 *      Remove the port from the system list.
 *      Cleanup and windows that are still using the Zoom Gadgets
 *      Unload the handler segment list.
 *      Notify the user that Zoom-Daemon is deactivated.
 *      Close Intuition.
 *    else (we could not replace the functions)
 *      Put back the input handler
 *      Inform the user that Zoom-Daemon can not be removed
 */
 
void main()
{
   struct MsgPort *NamedPort;

   NamedPort = FindPort(PORTNAME);
   if (NamedPort == NULL)
   {
      CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
      LoadHandler(&NamedPort);
      AddPort(NamedPort);
      SetVariables(NamedPort);
      SetVectors(NamedPort);
      TellInputDevice(IND_ADDHANDLER);
      SetupWindows();
      printf("%s v%d.%d.%d Installed\n",program,
         var(MajVers),var(MinVers),LOADVERS);
   } else {
      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);
         if (UnSetVectors(NamedPort))
         {
            RemPort(NamedPort);
            FreeAllZoomGadgets();
            UnLoadSeg(var(Segment));
            printf("%s removed\n",program);
            CloseLibrary(IntuitionBase);
         } else {
            TellInputDevice(IND_ADDHANDLER);
            printf("SetFunction vectors have been changed!\n");
            printf("Cannot remove %s\n",program);
         }
      }
   }
}
