/*
 *  WICONSETTER     A companion utility to wIconify.  wIconSetter allows
 *                  you to specify custom icons for windows ans screens
 *                  that normally use the default icons.
 *
 *  wIconSetter.c   Main loader code for wIconSetter.
 *
 *  Copyright 1990 by Davide P. Cervone, all rights reserved.
 *  You may use this code, provided this copyright notice is kept intact.
 */

#include "wIconSetter.h"

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 IconHandlerInfo *IconHandlerData;
static long Segment;                        /* The loaded handler segment */

ICONPROGRAM *FirstProgram;                  /* The list of icons */
ICONPROGRAM *ProgramAny;                    /* the program named [ANY] */
ICONDEFINE  *FirstDefine;                   /* the list of defined icons */



/*
 *  PrefixMatch()
 *
 *  Calls the handler's PrefixMatch routine, after checking
 *  for NAME_ANY matches.
 */

int PrefixMatch(s1,s2)
char *s1,*s2;
{
   if (s1 == NAME_ANY) return(s2 == NAME_ANY); else
   if (s2 == NAME_ANY) return(1);

   return(VAR(PrefixMatch)(s1,s2));
}


/*
 *  FreeImage()
 *
 *  If an image structure was given,
 *    If the image has allocated data, free it
 *    Free the image structure itself
 */

void FreeImage(theImage)
struct Image *theImage;
{
   if (theImage)
   {
      if (theImage->ImageData) FreeRaster(theImage->ImageData,
         theImage->Width,theImage->Height*theImage->Depth);
      FREESTRUCT(Image,theImage);
   }
}


/*
 *  FreeWindow()
 *
 *  Free any images associated with the window
 *  Free the window's icon's name, if any
 *  Free the window's name, if any
 *  Free the window structure itself
 */

void FreeWindow(theWindow)
ICONWINDOW *theWindow;
{
   if (theWindow->Flags & IW_IMAGE)   FreeImage(theWindow->Icon.Image);
   if (theWindow->Flags & IW_SELECT)  FreeImage(theWindow->Icon.Select);
   if (theWindow->Flags & IW_MASK)    FreeImage(theWindow->Mask);
   if (theWindow->Flags & IW_NAME)    FREECHAR(theWindow->Icon.Name);
   if (theWindow->Name != NAME_NULL && theWindow->Name != NAME_ANY &&
       theWindow->Name != SCREENICON) FREECHAR(theWindow->Name);
   FREESTRUCT(IconWindow,theWindow);
}


/*
 *  FreeScreen()
 *
 *  While the screen has more windows
 *    Free the window and move on to the next one
 *  Free the screen's name, if any
 *  Free the screen structure itself
 */

void FreeScreen(theScreen)
ICONSCREEN *theScreen;
{
   ICONWINDOW *nextWindow;
   
   while (theScreen->Window)
   {
      nextWindow = theScreen->Window->Next;
      FreeWindow(theScreen->Window);
      theScreen->Window = nextWindow;
   }
   if (theScreen->Name != NAME_NULL && theScreen->Name != NAME_ANY)
      FREECHAR(theScreen->Name);
   FREESTRUCT(IconScreen,theScreen);
}


/*
 *  FreeProgram()
 *
 *  While there are more screens associated with this program
 *    Free the screen and move on to the next one
 *  Free the program's name, if any
 *  Free the program structure itself
 */

void FreeProgram(theProg)
ICONPROGRAM *theProg;
{
   ICONSCREEN *nextScreen;
   
   while (theProg->Screen)
   {
      nextScreen = theProg->Screen->Next;
      FreeScreen(theProg->Screen);
      theProg->Screen = nextScreen;
   }
   if (theProg->Name != NAME_NULL && theProg->Name != NAME_ANY)
      FREECHAR(theProg->Name);
   FREESTRUCT(IconProgram,theProg);
}


/*
 *  FreeLists()
 *
 *  Convert the binary tree back into a linked list
 *  While there are programs in the list
 *    Free the program and move on to the next one
 *  If there is a ProgramAny, free it
 *  While there are more definitions in the list
 *    Free the definition and move on to the next one
 */

static void FreeLists()
{
   ICONPROGRAM *nextProg;
   ICONDEFINE  *nextDefine;
   extern ICONPROGRAM *TreeToList();
   
   FirstProgram = TreeToList(FirstProgram);
   while (FirstProgram)
   {
      nextProg = FirstProgram->Next;
      FreeProgram(FirstProgram);
      FirstProgram = nextProg;
   }
   if (ProgramAny) FreeProgram(ProgramAny);
   
   while (FirstDefine)
   {
      nextDefine = FirstDefine->Next;
      FreeDefine(FirstDefine);
      FirstDefine = nextDefine;
   }
}


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

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;
   }
   FreeLists();
   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);
}


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


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

static void SetVectors()
{
   VAR(OldOpenWindow) =
      SetFunction(IntuitionBase,&LVOOpenWindow,var(aOpenWindow));
   VAR(OldSetWindowTitles) =
      SetFunction(IntuitionBase,&LVOSetWindowTitles,var(aSetWindowTitles));
   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.
 */

static int UnSetVectors()
{
   long NewOpenWindow;
   long NewSetWindowTitles;
   long NewOpenScreen;
   int status = TRUE;
 
   NewOpenWindow = SetFunction(IntuitionBase,&LVOOpenWindow,VAR(OldOpenWindow));
   NewSetWindowTitles =
      SetFunction(IntuitionBase,&LVOSetWindowTitles,VAR(OldSetWindowTitles));
   NewOpenScreen = SetFunction(IntuitionBase,&LVOOpenScreen,VAR(OldOpenScreen));
   if (NewOpenWindow      != (long) var(aOpenWindow) ||
       NewSetWindowTitles != (long) var(aSetWindowTitles) ||
       NewOpenScreen      != (long) var(aOpenScreen))
   {
      SetFunction(IntuitionBase,&LVOOpenWindow,NewOpenWindow);
      SetFunction(IntuitionBase,&LVOSetWindowTitles,NewSetWindowTitles);
      SetFunction(IntuitionBase,&LVOOpenScreen,NewOpenScreen);
      status = FALSE;
   }
   return(status);
}


/*
 *  SetVariables()
 *
 *  The IconHandlerData 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(FirstDefine)   = FirstDefine;
   VAR(FirstProgram)  = FirstProgram;
   VAR(ProgramAny)    = ProgramAny;
}


/*
 *  GetVariables()
 *
 *  Look up the values stored in the IconHandlerData 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;
{
   IconHandlerData = (struct IconHandlerInfo *)thePort;
   IntuitionBase = VAR(IntuitionBase);
   FirstDefine   = VAR(FirstDefine);
   FirstProgram  = VAR(FirstProgram);
   ProgramAny    = VAR(ProgramAny);
   Segment       = var(Segment);
}


/*
 *  SetIcons()
 *
 *  For each window in the list of initial windows
 *    Get the autoiconify flag and remove it from the icon
 *    Set the given window to the specified icon
 *    If autoiconify was set
 *      Iconify the window and replace the autoiconify flag
 */

static void SetIcons(InitIcon,i)
struct NeedsIcon InitIcon[MAXICONS];
short i;
{
   ULONG AutoIconify; 
   
   while (i--)
   {
      AutoIconify = InitIcon[i].Icon->Flags & WI_AUTOICONIFY;
      InitIcon[i].Icon->Flags -= AutoIconify;
      wSetIcon(InitIcon[i].Window,InitIcon[i].Icon);
      if (AutoIconify)
      {
         wIconify(InitIcon[i].Window);
         InitIcon[i].Icon->Flags |= AutoIconify;
      }
   }
}


/*
 *  SetInitialIcons()
 *
 *  Forbid() so that nothing happens while we're looking at the lists
 *  Start with the first screen of intutition
 *  While there are more screens to check
 *    Start with the screen's first window
 *    While there are more windows to check
 *      Get the port used by the window
 *      If it has a port, and the port has a signal-taks, and the port's
 *        action is to signal the task, then get the name of the process
 *        that owns the port
 *      Otherwise
 *        Use the name [NULL]
 *      Find the program/screen/window combination in the list
 *      If it was found and ther eis more room in the list
 *        Record the window and icon so we can add it later
 *      Move on to the next window
 *    Move on to the next screen
 *  Permit
 *  If there were icons to add, then add them (we postpone the adding
 *    until now since it requires wIconify-Handler to be running, but
 *    we wanted to stay in the Forbid() state without interuption).
 */

static void SetInitialIcons()
{
   struct NeedsIcon InitIcon[MAXICONS];
   short i = 0;
   struct Screen *theScreen;
   struct Window *theWindow;
   struct MsgPort *thePort;
   char Program[MAXNAME];
   ICONWINDOW *theIcon;
   
   Forbid();
   theScreen = IntuitionBase->FirstScreen;
   while (theScreen)
   {
      theWindow = theScreen->FirstWindow;
      while (theWindow)
      {
         thePort = theWindow->UserPort;
         if (thePort && thePort->mp_SigTask != NULL &&
            (thePort->mp_Flags & PF_ACTION) == PA_SIGNAL)
                VAR(GetProgramName)(Program,thePort->mp_SigTask);
               else
                strcpy(Program,"[NULL]");
         theIcon = VAR(FindIcon)(Program,theScreen,theWindow->Title);
         if (theIcon && i<MAXICONS)
         {
            InitIcon[i].Window = theWindow;
            InitIcon[i].Icon   = &(theIcon->Icon);
            i++;
         }
         theWindow = theWindow->NextWindow;
      }
      theScreen = theScreen->NextScreen;
   }
   Permit();
   if (i) SetIcons(InitIcon,i);
}



/*
 *  StillRunning()
 *
 *  If we have an error message to print
 *    Print it and indicate that the handler is still active
 *  Clear the lists and other variables so that they are not freed during exit
 */

static void StillRunning(s)
char *s;
{
   if (s)
   {
      printf(s);
      printf("\n%s not removed\n",program);
   }
   ProgramAny = NULL;
   FirstProgram = NULL;
   FirstDefine  = NULL;
   IntuitionBase = NULL;
   Segment = NULL;
}


/*
 *  InstallHandler()
 *
 *  If there are too many arguments, exit with the USAGE message
 *  Load the handler
 *  If an initialization file was specified, read it
 *  Otherwise try to read the default initialization file
 *  If a program was specified in the init file
 *    If wIconify is running
 *      If wIconSetter is NOT already running
 *        Add the wIconSetter port to the system list so we can find it later
 *        Set the variables needed by the handler
 *        Trap the Intuition library vectors used by the handler
 *        Check if any existing windows need icons
 *        Let the user know that wIconSetter is installed
 *        Exit without freeing memory used by the handler
 *  Otherwise report an appropriate message
 */

static void InstallHandler(argc,argv)
int argc;
char **argv;
{
   struct MsgPort *NamedPort;

   if (argc > 2) DoExit("Usage:  '%s'",USAGE);

   LoadHandler(&NamedPort);

   if (argc == 2)
      ReadInitFile(argv[1],NULL);
     else
      ReadInitFile(INITFILE1,INITFILE2);

   if (FirstProgram || ProgramAny)
   {
      if (wIconifyActive())
      {
         if (FindPort(PORTNAME) == NULL)
         {
            AddPort(NamedPort);
            SetVariables(NamedPort);
            SetVectors();
            SetInitialIcons();
            printf("%s v%d.%d.%d Installed\n",program,
               var(MajVers),var(MinVers),LOADVERS);
            StillRunning(NULL);
         } else printf("Attempt to re-install %s!\n",program);
      } else printf("wIconify not running or incompatible version\n");
   } else printf("No icons specified in initialization file\n");
}


/*
 *  RemoveHandler()
 *
 *  Retrieve the variables that the loader set up initially
 *  If the versions don't match, report an error
 *  Otherwise
 *    If we can remove the SetFunction() traps
 *       If wIconify is still active
 *         Put back the trapped vectors
 *         Inform the user that we can't exit (wIconify amy still be using
 *         the icons that we have set up)
 *       Otherwise
 *         Remove the port from the system list
 *         Let the user know we've been deactivated
 *     Otherwise report an error
 */

static void RemoveHandler(NamedPort)
struct MsgPort *NamedPort;
{
   GetVariables(NamedPort);
   if (var(MinLoadVers) > LOADVERS || var(MajVers) < MINHMAJVERS ||
      (var(MajVers) == MINHMAJVERS && var(MinVers) < MINHMINVERS))
         StillRunning("Loader version mismatch");
   else
   {
      if (UnSetVectors())
      {
         if (wIconifyActive())
         {
            SetVectors();
            StillRunning("Can't cancel wIconSetter while wIconify is active");
         } else {
            RemPort(NamedPort);
            printf("%s removed\n",program);
         }
      } else StillRunning("SetFunction vectors have been changed!");
   }
}


/*
 *  main()
 *
 *  Open the necessary libraries
 *  Look in the system list to see if wIconSetter is already running
 *  If not running, try to install the handler,
 *  Otherwise try to cancel the handler
 */

void main(argc,argv)
int argc;
char **argv;
{
   struct MsgPort *NamedPort;

   CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
   CheckLibOpen(&GfxBase,"graphics.library",GRAPHICS_REV);

   NamedPort = FindPort(PORTNAME);
   if (NamedPort == NULL)
      InstallHandler(argc,argv);
     else
      RemoveHandler(NamedPort);
   DoExit(NULL);
}
