/*
 *  VSCREEN.C       Creates virtual screens that can be larger than
 *                  the actual display area of your monitor.  The virtual
 *                  screen scrolls when the mouse moves off the edge of
 *                  the display.
 *
 *                  Copyright 1988 by Davide P. Cervone, all rights reserved.
 *
 *                  You may distibute this code as is, for non-commercial
 *                  purposes, provided that this notice remains intact and the
 *                  documentation file is distributed with it.
 *
 *                  You may not modify this code nor incorporate it into your
 *                  own programs without the permission of the author.
 */

/*
 *  WARNING:  This code uses and even modifies the INTUITIONPRIVATE
 *  area of the IntuitionBase structure.  Use it at your own risk.  It is
 *  likely to break under a future release of Intuition.
 */

#include "vScreen.h"

static char *program = "vScreen";           /* the program name */
static char *author  = COPYRIGHT;           /* the copyright notice */
#define LOADVERS        1                   /* this program's version */

static char *handler = "L:vScreen-Handler"; /* the name of the handler */
#define HANDLER         &(handler[2])       /* same but in the current dir */


extern struct LayersBase *LayersBase;       /* the Layers library */
 
struct vScreenInfo *vScreenData;            /* data needed by the handler */
struct Screen *VScreen = NULL;              /* the virtual screen */
static long Segment = NULL;                 /* the loaded handler */
SHORT ScreenWidth,ScreenHeight;             /* the new screen sizes */

static struct MsgPort *NamedPort = NULL;    /* used to find the handler later */
static struct MsgPort *InputPort = NULL;    /* to talk to Input.Device */
static struct IOStdReq *InputBlock = NULL;  /* IO block for Input.Device */
static short  InputDevice = FALSE;          /* is Input.Device open? */
static int    Enlarged = FALSE;             /* is screen changed? */


/*
 *  These routines are in vScreenSetup.c
 */

extern struct Screen *FindScreen();
extern void SetVariables();
extern void GetVariables();
extern int  EnlargeScreen();
extern void RestoreScreen();

 
#ifndef PROTO
extern long SetFunction();
#endif

/*
 *  We trap these routines via SetFunction in order to make vSscreen work.
 */

#ifndef PROTO
extern void MoveSprite();
extern void LoadView();
extern void AutoRequest();
extern void BuildSysRequest();
extern void CloseScreen();
#endif

extern long LVOMoveSprite;
extern long LVOLoadView;
extern long LVOAutoRequest;
extern long LVOBuildSysRequest;
extern long LVOCloseScreen;


/*
 *  CreateNonSigPort()
 *
 *  Creates a message port with signal type PA_IGNORE.  Based on
 *  CreatePort() from the exec support functions documented in the RKM.
 */

struct MsgPort *CreateNonSigPort(name,pri)
char *name;
BYTE pri;
{
   struct MsgPort *thePort;
   
   thePort = (struct MsgPort *)AllocMem((ULONG)sizeof(struct MsgPort),
                                         MEMF_PUBLIC | MEMF_CLEAR);
   if (thePort)
   {
      thePort->mp_Node.ln_Name = name;
      thePort->mp_Node.ln_Pri  = pri;
      thePort->mp_Node.ln_Type = NT_MSGPORT;
      
      thePort->mp_Flags = PA_IGNORE;
      thePort->mp_SigBit = 0;
      thePort->mp_SigTask = NULL;
      
      if (name)
         AddPort(thePort);
        else
         NewList(&(thePort->mp_MsgList));
   }
   return(thePort);
}


/*
 *  DeleteNonSigPort()
 *
 *  Deletes a message port with signal type PA_IGNORE.  Based on
 *  DeletePort() from the exec support functions documented in the RKM
 */

void DeleteNonSigPort(thePort)
struct MsgPort *thePort;
{
   if (thePort->mp_Node.ln_Name) RemPort(thePort);
   thePort->mp_Node.ln_Type = 0xFF;
   thePort->mp_MsgList.lh_Head = (struct Node *) -1;
   FreeMem(thePort,(ULONG)sizeof(struct MsgPort));
}


/*
 *  DoExit()
 *
 *  Exit with error status.  Print a message with up to three parameters
 *  and then clean up everything (restore the screen if it is enlarged,
 *  unload the handler if it is loaded, close the Input.Device and free
 *  its IO blocks and ports, and close any open libraries).  Return the
 *  error status.
 */

void DoExit(s,x1,x2,x3)
char *s, *x1,*x2,*x3;
{
   int status = OK_EXIT;

   if (s)
   {
      printf(s,x1,x2,x3);
      printf("\n");
      status = ERROR_EXIT;
   }
   if (Enlarged)        RestoreScreen();
   if (NamedPort)       DeleteNonSigPort(NamedPort);
   if (Segment)         UnLoadSeg(Segment);
   if (InputDevice)     CloseDevice(InputBlock);
   if (InputBlock)      DeleteStdIO(InputBlock);
   if (InputPort)       DeletePort(InputPort);
   if (IntuitionBase)   CloseLibrary(IntuitionBase);
   if (GfxBase)         CloseLibrary(GfxBase);
   if (LayersBase)      CloseLibrary(LayersBase);
   exit(status);
}


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

void CheckLibOpen(lib,name,rev)
APTR *lib;
char *name;
int rev;
{
   #ifndef PROTO
   extern APTR OpenLibrary();
   #endif
   
   if ((*lib = OpenLibrary(name,(ULONG)rev)) == NULL)
      DoExit("Can't open '%s'",name);
}


/*
 *  GetInt()
 *
 *  Read the first integer from the given string variable (used to parse
 *  the command-line arguments).  If no integer can be read, exit and
 *  show the usage string.
 */

static long GetInt(s)
char *s;
{
   long i;

   if (sscanf(s,"%ld",&i) != 1) DoExit("Usage:  %s",USAGE);
   return(i);
}


/*
 *  ParseArguments()
 *
 *  Parse the command-line arguments.  If there are too many or too few
 *  arguments, exit with an error message, otherwise get the width and height
 *  agruments.  If there was a screen name specified, record that.
 *  Find the specified screen.
 */

static void ParseArguments(argc,argv)
int argc;
char *argv[];
{
   char *ScreenName = NULL;

   if (argc < 3) DoExit("Width and Height are required\nUsage:  %s",USAGE);
   if (argc > 4) DoExit("Too many arguments");
   ScreenWidth  = GetInt(argv[1]);
   ScreenHeight = GetInt(argv[2]);
   if (ScreenWidth <= 0 || ScreenHeight <= 0)
      DoExit("Screen height and width must be positive");
   if (ScreenWidth > 1024 || ScreenHeight > 1024)
      printf("Warning:  sizes greater than 1024 may cause Blitter problems\n");
   if (argc > 3 && argv[3] && argv[3][0] != '\0') ScreenName = argv[3];
   VScreen = FindScreen(ScreenName);
}


/*
 *  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 a 
 *  message.  Once the handler is loaded, call its Setup routine passing it
 *  our version number.  The handler will check the versions for compatability,
 *  and return NULL for version mismatch, or a pointer to its data structure
 *  with pointers to the variables that vScreen will initialize for it.
 *  LoadHandler() sets the pointer to the handlers SegList, and sets the
 *  loader version number
 */

static void LoadHandler()
{
   struct vScreenInfo *(*Setup)();

   if ((Segment = LoadSeg(HANDLER)) == NULL)
      if ((Segment = LoadSeg(handler)) == NULL)
         DoExit("Can't Load '%s'",handler);
   Setup = (struct vScreenInfo *(*)()) ((Segment << 2) + 4);
   
   vScreenData   = (*Setup)(LOADVERS);
   if (vScreenData)
   {
      if (var(MajVers) > 1) DoExit("version mismatch with '%s'",HANDLER);
   } else {
      DoExit("'%s' reports a version mismatch",HANDLER);
   }

   var(Segment)  = Segment;
   var(LoadVers) = LOADVERS;
}


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

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

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


/*
 *  SetVectors()
 *
 *  Call SetFunction() to replace the library vectores for the routines that
 *  we need to trap.  Use the routines that the handler has supplied (they
 *  were passed to us in the vScreenData structure).  Save the old vectors
 *  in the vScreenData strucutre so we can replace them later.  Set the 
 *  jump addresses so that the stub routines can call the old vectors.
 *  I Know this is a kludge, and is a form of self-modifying code, but I
 *  can't figure out a better method.  The JSR (Ax) is only good when there
 *  is a free A register, which is not always the case.
 */

void SetVectors()
{
   var(OldCloseScreen) =
       SetFunction(IntuitionBase,&LVOCloseScreen,var(aCloseScreen));
   var(CloseScreenJmpTarget)[-1] = (long) var(OldCloseScreen);

   var(OldBuildSysRequest) =
       SetFunction(IntuitionBase,&LVOBuildSysRequest,var(aBuildSysRequest));
   var(BuildSysRequestJmpTarget)[-1] = (long) var(OldBuildSysRequest);

   var(OldAutoRequest) =
      SetFunction(IntuitionBase,&LVOAutoRequest,var(aAutoRequest));
   var(AutoRequestJmpTarget)[-1] = (long) var(OldAutoRequest);

   var(OldLoadView) = SetFunction(GfxBase,&LVOLoadView,var(aLoadView));
   var(LoadViewJmpTarget)[-1] = (long) var(OldLoadView);

   var(OldMoveSprite) = SetFunction(GfxBase,&LVOMoveSprite,var(aMoveSprite));
   var(MoveSpriteJmpTarget)[-1] = (long) var(OldMoveSprite);
}


/*
 *  UnSetVectors()
 *
 *  Put back the old jump vectors for the routines that we replaced.
 *  Make sure that no one else has replced them behind our backs, however.
 *  If they are not the same way we left them, return an error status.
 */

int UnSetVectors()
{
   long NewCloseScreen,NewBuildSysRequest,NewAutoRequest,
        NewLoadView,NewMoveSprite;
   int status = TRUE;

   NewCloseScreen =
      SetFunction(IntuitionBase,&LVOCloseScreen,var(OldCloseScreen));
   NewBuildSysRequest =
      SetFunction(IntuitionBase,&LVOBuildSysRequest,var(OldBuildSysRequest));
   NewAutoRequest =
      SetFunction(IntuitionBase,&LVOAutoRequest,var(OldAutoRequest));
   NewLoadView   = SetFunction(GfxBase,&LVOLoadView,var(OldLoadView));
   NewMoveSprite = SetFunction(GfxBase,&LVOMoveSprite,var(OldMoveSprite));

   if (NewCloseScreen     != (long) var(aCloseScreen) ||
       NewBuildSysRequest != (long) var(aBuildSysRequest) ||
       NewAutoRequest     != (long) var(aAutoRequest) ||
       NewLoadView        != (long) var(aLoadView) ||
       NewMoveSprite      != (long) var(aMoveSprite))
   {
      SetFunction(IntuitionBase,&LVOCloseScreen,NewCloseScreen);
      SetFunction(IntuitionBase,&LVOBuildSysRequest,NewBuildSysRequest);
      SetFunction(IntuitionBase,&LVOAutoRequest,NewAutoRequest);
      SetFunction(GfxBase,&LVOLoadView,NewLoadView);
      SetFunction(GfxBase,&LVOMoveSprite,NewMoveSprite);
      status = FALSE;
   }
   return(status);
}


/*
 *  main()
 *
 *  Look for the vScreen port.
 *  If one does not exist then vScreen is not currently active, so
 *    open the intuition and graphics libraries.
 *    Parse the command-line arguments to get the width, height, and screen.
 *    Load the handler code, and check its version.
 *    Create the named port used to store information about the handler.
 *    Setup some of the variables needed by the handler, and save a pointer
 *      to the handler data in the named port.
 *    Try to enlarge the size of the screen bitmap, and set more variables.
 *    Add the input handler into the input chain.
 *    SetFunction the neede vectors.  At this point vScreen is active.
 *    Print a message that reports the version numbers.
 *  Otherwise (the port already exists, so vScreen already is active)
 *    Get the pointer to the vScreenData structure from the port.
 *    If they user had supplied arguments, tell him vScreen already is running.
 *    Try to unset the routines we replaced earlier.
 *    If the vectors we removed successfully then
 *      Remove the input handler.
 *      Restore the screen to its original size.
 *      Delete the (no-longer-needed) named port.
 *      Unload the handler code.
 *      Tell the user that the code is removed.
 *      Close the libraries (note that these remained open between calls 
 *        to vScreen, while the handler was active).
 *    Otherwise (we could not replce the function vectors)
 *      Report the problem, and leave the handler running.
 */

void main(argc,argv)
int argc;
char *argv[];
{
   NamedPort = FindPort(PORTNAME);
   if (NamedPort == NULL)
   {
      CheckLibOpen(&IntuitionBase,"intuition.library",INTUITION_REV);
      CheckLibOpen(&GfxBase,"graphics.library",GRAPHICS_REV);

      ParseArguments(argc,argv);
      LoadHandler();
      if ((NamedPort = CreateNonSigPort(var(PortName),0L)) == NULL)
         DoExit("Can't Create Message Port '%s'",var(PortName));

      SetVariables(NamedPort);
      Enlarged = EnlargeScreen();
      TellInputDevice(IND_ADDHANDLER);
      SetVectors();

      printf("%s v%d.%d.%d Installed\n",program,
         var(MajVers),var(MinVers),var(LoadVers));
   } else {
      GetVariables(NamedPort);
      if (argc > 1)
      {
         printf("%s already active on screen '%s'\n",program,VScreen->Title);
      } else {
         if (UnSetVectors())
         {
            TellInputDevice(IND_REMHANDLER);
            RestoreScreen();
            DeleteNonSigPort(NamedPort);
            UnLoadSeg(var(Segment));
            printf("%s Removed\n",program);
            CloseLibrary(IntuitionBase);
            CloseLibrary(GfxBase);
         } else {
            printf("SetFunction vectors have been changed!\n");
            printf("%s Not Removed\n",program);
         }
      }
   }
}
