/*
 *  NL-Daemon   A program to force old programs to use NL gadget imagery.
 *
 *              Copyright 1989 by Davide P. Cervone.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "NL-Main.h"
#include "NL-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 NL_HandlerInfo *NL_HandlerData; /* data shared with the handler */
static long Segment;                        /* the loaded handler segment */

/*
 *  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 (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 NL_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 NL_HandlerInfo *(*Setup)();
   
   if ((Segment = LoadSeg(HANDLER)) == NULL)
      if ((Segment = LoadSeg(handler)) == NULL)
        DoExit("Can't load %s",handler);
   Setup = (struct NL_HandlerInfo *(*)()) ((Segment << 2) + 4);
   
   NL_HandlerData = (*Setup)(LOADVERS);
   if (NL_HandlerData)
   {
      if (var(MajVers) < MINHMAJVERS ||
         (var(MajVers) == MINHMAJVERS && var(MinVers) < MINHMINVERS))
             DoExit("Version mismatch with %s",HANDLER);
      *thePort = &(NL_HandlerData->NL_Port);
   } else {
      DoExit("%s reports a version mismatch",HANDLER);
   }
   
   var(Segment)  = Segment;
}


/*
 *  InvertImage()
 *
 *  Performs a Logical NOT on all the bits in the Image data, and
 *  XORs the second bitplane in the PlaneOnOff field.  This makes the
 *  standard Intuition Imagery use color 2 as the background and color
 *  three as the forground (rather than 1 and 0).
 */

#define IMAGESIZE(i)    ((i)->Depth*(((i)->Width+15)>>4)*(i)->Height)

static void InvertImage(theImage)
struct Image *theImage;
{
   short i;
   USHORT *theWord;
   
   if (theImage)
   {
      theWord = theImage->ImageData;
      for (i=IMAGESIZE(theImage); i; i--,theWord++) *theWord = ~(*theWord);
      theImage->PlaneOnOff ^= 0x02;
   }
}


/*
 *  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(OldSetMenuStrip) =
      SetFunction(IntuitionBase,&LVOSetMenuStrip,var(aSetMenuStrip));
   VAR(OldOpenScreen) =
      SetFunction(IntuitionBase,&LVOOpenScreen,var(aOpenScreen));
}


/*
 *  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 NewSetMenuStrip;
   long NewOpenScreen;
   int status = TRUE;

   NewOpenWindow = SetFunction(IntuitionBase,&LVOOpenWindow,VAR(OldOpenWindow));
   NewSetMenuStrip =
      SetFunction(IntuitionBase,&LVOSetMenuStrip,VAR(OldSetMenuStrip));
   NewOpenScreen = SetFunction(IntuitionBase,&LVOOpenScreen,VAR(OldOpenScreen));
   if (NewOpenWindow   != (long) var(aOpenWindow) ||
       NewSetMenuStrip != (long) var(aSetMenuStrip) ||
       NewOpenScreen   != (long) var(aOpenScreen))
   {
      SetFunction(IntuitionBase,&LVOOpenWindow,NewOpenWindow);
      SetFunction(IntuitionBase,&LVOSetMenuStrip,NewSetMenuStrip);
      SetFunction(IntuitionBase,&LVOOpenScreen,NewOpenScreen);
      status = FALSE;
   }
   return(status);
}


/*
 *  SetVariables()
 *
 *  The NL_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,argc)
struct MsgPort *thePort;
int argc;
{
   VAR(IntuitionBase) = IntuitionBase;
   VAR(SysBase) = SysBase;

   if (argc == 1)
   {
      Forbid();
      InvertImage(IntuitionBase->CheckImage[0]);
      InvertImage(IntuitionBase->CheckImage[1]);
      InvertImage(IntuitionBase->AmigaIcon[0]);
      InvertImage(IntuitionBase->AmigaIcon[1]);
      Permit();
      var(Flags) |= INTUITION_CHANGED;
   }
}


/*
 *  GetVariables()
 *
 *  Look up the values stored in the NL_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;
{
   NL_HandlerData = (struct NL_HandlerInfo *)thePort;
   IntuitionBase = VAR(IntuitionBase);
}


/*
 *  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.  If the window has a menu strip, remove 
 *  it and then replace it (our SetMenuStrip routine will fix the menu
 *  for us, since it has already be added in via SetFunction).
 *  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;
   struct Menu *theMenu;
   
   if (var(SetupWindow) || var(SetupScreen))
   {
      Forbid();
      theScreen = IntuitionBase->FirstScreen;
      while (theScreen)
      {
         if (var(SetupScreen)) VAR(SetupScreen)(theScreen);
         if (var(SetupWindow))
         {
            theWindow = theScreen->FirstWindow;
            while (theWindow)
            {
               VAR(SetupWindow)(theWindow);
               if (theWindow->MenuStrip)
               {
                  theMenu = theWindow->MenuStrip;
                  ClearMenuStrip(theWindow);
                  SetMenuStrip(theWindow,theMenu);
               }
               theWindow = theWindow->NextWindow;
            }
         }
         theScreen = theScreen->NextScreen;
      }
      Permit();
   }
}


/*
 *  CheckGadget()
 *
 *  Checks to see if a gadget's imagery has been changed.  If so, it
 *  replaces the imagery with the standard Intuition imagery that we saved
 *  when we set up the NL gadget.
 */

static int CheckGadget(theGadget,HiResImage,LowResImage)
struct Gadget *theGadget;
APTR HiResImage,LowResImage;
{
   int status = FALSE;

   if (theGadget->GadgetRender == HiResImage ||
       theGadget->GadgetRender == LowResImage)
   {
      status = TRUE;
      theGadget->GadgetRender = theGadget->SelectRender;
      theGadget->LeftEdge = ((LONG)theGadget->UserData) >> 16;
      theGadget->Width    = ((LONG)theGadget->UserData) & 0xFFFF;
   }
   return(status);
}


/*
 *  CheckGadgetList()
 *
 *  Looks through a list of gadgets to see if any of their imagery has been
 *  changed, and replaces their original imagery if they where changed.
 *  Returns TRUE if any changes were made, FALSE otherwise.
 */

static int CheckGadgetList(theGadget)
struct Gadget *theGadget;
{
   int Changed = FALSE;

   while (theGadget)
   {
      switch(theGadget->GadgetType & ~GADGETTYPE)
      {
         case SIZING:
            Changed |= CheckGadget(theGadget,var(SizeImage),var(LR_SizeImage));
            break;

         case WUPFRONT:
         case SUPFRONT:
            Changed |=
               CheckGadget(theGadget,var(UpFrontImage),var(LR_UpFrontImage));
            break;

         case WDOWNBACK:
         case SDOWNBACK:
            Changed |=
               CheckGadget(theGadget,var(DownBackImage),var(LR_DownBackImage));
            break;

         case CLOSE:
            Changed |=
               CheckGadget(theGadget,var(CloseImage),var(LR_CloseImage));
            break;

         case BOOLGADGET:
            Changed |= CheckGadget(theGadget,var(ZoomImage),var(LR_ZoomImage));
            break;
      }
      theGadget = theGadget->NextGadget;
   }
   return(Changed);
}


/*
 *  CleanUpWindows()
 *
 *  Looks through the intuition Screen and Window lists to see if
 *  any gadgets are still using the NL gadget imagery.  If so, 
 *  replace the old imagery and detail pen, then refresh the window
 *  to show the original gadgets.  Do this all in Forbid() mode, so 
 *  nothing changes while we're looking at the list.
 */

static void CleanUpWindows()
{
   struct Screen *theScreen;
   struct Window *theWindow;
   int Changed;

   Forbid();
   theScreen = IntuitionBase->FirstScreen;
   while (theScreen)
   {
      theWindow = theScreen->FirstWindow;
      while (theWindow)
      {
         Changed = CheckGadgetList(theWindow->FirstGadget);
         if (theWindow->BlockPen & PENCHANGED)
         {
            theWindow->BlockPen  = STDBLOCKPEN;
            if (theWindow->DetailPen & PENCHANGED)
               theWindow->DetailPen = STDDETAILPEN;
            Changed = TRUE;
         }
         if (Changed) RefreshWindowFrame(theWindow);
         theWindow = theWindow->NextWindow;
      }

      Changed = CheckGadgetList(theScreen->FirstGadget);
      if (theScreen->BlockPen & PENCHANGED)
      {
         theScreen->BlockPen  = STDBLOCKPEN;
         if (theScreen->DetailPen & PENCHANGED)
            theScreen->DetailPen = STDDETAILPEN;
         Changed = TRUE;
      }
      if (Changed)
         ShowTitle(theScreen,(theScreen->Flags & SHOWTITLE)? TRUE: FALSE);
      theScreen = theScreen->NextScreen;
   }

   if (var(Flags) & INTUITION_CHANGED)
   {
      InvertImage(IntuitionBase->CheckImage[0]);
      InvertImage(IntuitionBase->CheckImage[1]);
      InvertImage(IntuitionBase->AmigaIcon[0]);
      InvertImage(IntuitionBase->AmigaIcon[1]);
      var(Flags) &= ~INTUITION_CHANGED;
   }
   Permit();
}


/*
 *  Main()
 *
 *  Look for the NL-Daemon port, which indicates that NL-Daemon already exists.
 *  If the port does not exist, then NL-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
 *    Retro-fit any existing windows to include the NL gadgets
 *    Notify the user that all is ready.
 *  else (the port already exists, so NL-Daemon alreay is active)
 *    Get the pointer to the NL_HandlerData structure from the port,
 *      and get any variables we need from the structure.
 *    Check that the loader versions are compatible.
 *    Try to remove the SetFunction calls.
 *    If successfull, then
 *      Remove the port from the system list.
 *      Cleanup and windows that are still using the image data
 *      Unload the handler segment list.
 *      Notify the user that NL-Daemon is deactivated.
 *      Close Intuition.
 *    else (we could not replace the functions)
 *      Inform the user that NL-Daemon can not be removed
 */
 
void main(argc,argv)
int argc;
char **argv;
{
   struct MsgPort *NamedPort;

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