/*
    SAS/C Background Example
    SAS/C Help System Hot-Key Example
    version 6.0   1992

    This is a program which demonstrates how to write a background process which
    employs a hot-key.  This program will pop up the main interface to the
    SAS/C Help System when control-Help is pressed. 

    You should compile this program with the following options:

               NOSTACKCHECK
               STRUCTUREEQUIVALENCE
               LINK
               STARTUP=cback

    NOSTACKCHECK is suggested because the function handlerStuff can be called
    from a task other than this program.  It would be required if the function
    was not declared with the __interrupt keyword.  This is discussed in more
    detail at the function's definition.

    STRUCTUREEQUIVALENCE is not required, but it does circumvent several
    warnings that would otherwise be produced.

    STARTUP=cback tells slink to link with the background startup code.  This
    is required to produce a background process.  If you link in a separate step,
    you must link with cback.o instead of c.o.

    You should not compile this program with the NOSTANDARDIO option
    because the program's main routine is called "__main".  NOSTANDARDIO
    can only be used on programs whose main routine is called "main".
    This program does not initialize stdin, stdout or stderr, so it
    cannot call printf() or other standard IO functions.  Calling
    these functions will probably crash the machine.

    You may notice that this program does not open any AmigaDOS libraries.  Instead
    it takes advantage of the fact that the version 6.0 compiler now opens
    needed libraries for you.

    ****  IMPORTANT  ***
    When running CPR on this program or other programs using __main, you
    must perform the following 2 steps to avoid crashing the machine:
    
           1)  As soon as cpr is invoked, select Catch New Tasks from the
               Options menu.  Catch New Tasks must be set to on.
               
           2)  Type 
                    go __main
                    
               This places you at the beginning of your code.  If you say
               go __main without setting Catch New Tasks to on, you will
               probably crash the machine.  
               
     If you try to debug the input handler program HandlerInterface in this
     program, you will probably crash your machine. 

     The schelp command takes the following forms:
     
                 schelp
     
     This command will load schelp as a background process, display a copyright
     banner, and display the usage information.  If schelp is already loaded,
     it will simply display the usage information.
     
                 schelp quit
                 
     This command will display a terminating message and unload schelp from
     memory.            
     
                 schelp <command>
                 
     This command will replace the command currently tied to the control-Help
     key with the command designated.  In order for the command to work, it 
     must contain the full path to the command.            

*/

/**************************  INCLUDES  ************************/
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/interrupts.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/tasks.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <devices/timer.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <intuition/intuition.h>
#include <libraries/dos.h>
#include <graphics/gfxmacros.h>
#include <hardware/custom.h>
#include <hardware/dmabits.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <string.h>
#include <stdlib.h>




/**************************  CONSTANTS  ***********************/
#define BANNER       "\x9B""0;33mSAS/C Help\x9B""0m Copyright \xA9 1992 SAS Institute, Inc.\n"
#define BANNER1  "Usage: \x9B""1mschelp\x9B""0m [quit] [<command>]\nwhere\n\x9B""1mschelp\x9B""0m           allows you to hit cntrl-Help to pop up the SAS/C Help screen\n\x9B""1mschelp quit\x9B""0m      deletes schelp as a background process\n"
#define BANNER2  "\x9B""1mschelp <command>\x9B""0m allows you to change the command executed when you hit cntrl-Help\n                   *** Include full path to the command\n"
#define DEFCMD       "sc:c/amigaguide sc:help/sc_help.guide\0"
#define DEFKEY       0x5F
#define KILLMSG      "\x9B""1mSAS/C Help\x9B""0m Terminating\n"
#define MAXCMD       200
#define PORTNAME     "SASC_HELP.port"



/*********************** GLOBAL VARIABLES *********************/
typedef struct
   {
   struct Task          *buddy;
   ULONG                 helpsig;
   short                 helpsignum;
   short                 key;
   } GLOBAL_DATA;

struct OURMSG {
 struct Message msg_header;
 short  quit;
 char   cmd[MAXCMD];
 };


typedef void (*vfunc)();


/**************************  PROTOTYPES  **********************/
void clean_exit(struct IOStdReq *, struct Interrupt, GLOBAL_DATA,
                struct MsgPort *, BPTR, BPTR, struct MsgPort *,int);

struct InputEvent * __asm HandlerInterface(register __a0 struct InputEvent *,
                                    register __a1 GLOBAL_DATA *);

struct IOStdReq *CreateIOReq(struct MsgPort *, int);

void DeleteIOReq(struct IOStdReq *);



/**********************  CBACK DECLARATIONS  ******************/
long __stack = 4000;              /* Amount of stack space our task needs */
char *__procname = "SAS/C Help";  /* The name of the task to create       */
long __priority = 20;             /* The priority to run the task at    */
long __BackGroundIO = 1;          /* Flag indicating we want to send I/O to the
                                     original shell.  We will print a banner.
                                     NOTE:  This variable may also be called
                                     _BackGroundIO.  Notice the single
                                     underscore.                       */
extern BPTR _Backstdout;          /* File handle pointing to originating shell
                                     (standard output when run in background) */

/********************************************************************/

void __stdargs __main(char *command)
{
   struct OURMSG *msg, tmpmsg;
   struct MsgPort *port;
   int first_call = 0;

   char cmdstr[MAXCMD];
   BPTR  nullfh1, nullfh2;
   ULONG sig;
   struct MsgPort     *inputDevPort;
   struct IOStdReq    *inputRequestBlock;
   struct Interrupt   handlerStuff;
   GLOBAL_DATA global;

   global.helpsignum  = -1;
   inputDevPort        = NULL;
   inputRequestBlock   = NULL;
   nullfh1 = nullfh2 = NULL;

   /* Check if this program is already loaded, and if not, create a port  */
   if ((port = FindPort(PORTNAME)) == NULL)
      {
      /* Set flag indicating this is the first time the program's been called */
      first_call = 1;

      /* Since this is the first call, open the port */
      if ((port = CreatePort(PORTNAME,0)) == NULL)
          clean_exit(inputRequestBlock, handlerStuff, global, inputDevPort,
                nullfh1, nullfh2, port, first_call);
      }


   /* Allocate memory for and partially set up the message this program
      will send to itself.                                             */
   if ((msg = (struct OURMSG *)
              AllocMem(sizeof(struct OURMSG), MEMF_CLEAR | MEMF_PUBLIC)) == NULL)
      clean_exit(inputRequestBlock, handlerStuff, global, inputDevPort,
                 nullfh1, nullfh2, port, first_call);

   msg->msg_header.mn_Length = sizeof(struct OURMSG);
   msg->quit = 0;
   msg->cmd[0] = 0;

   /*  Set up the command to be executed to start SAS/C Help  */
   strcpy(cmdstr, DEFCMD);

   /* If this program was run from a CLI, then output the banner
      and process any parameters */
   if (command && *command)
      {
      /* Write the copyright banner if this is the first time the program
         was called.                                                     */
      if (first_call && _Backstdout)
         Write(_Backstdout, BANNER, sizeof(BANNER));

      /* Skip to a parameter if one exists */
      while(*command != ' ')
         command++;
      while(*command == ' ')
         command++;


      /* Evaluate the parameter */
      if (!stricmp(command, "QUIT\n"))
         {
         msg->quit = 1;
         if (_Backstdout)
            Write(_Backstdout, KILLMSG, sizeof(KILLMSG));
         }

      /*  If all that is left is a carriage return, print out the usage message */
      else if ((*command < ' ') && _Backstdout)
         {
         Write(_Backstdout, BANNER1, sizeof(BANNER1));
         Write(_Backstdout, BANNER2, sizeof(BANNER2));
         }
      /* Put the rest of the command into the command string to be executed  */
      else
         {
         strcpy(msg->cmd, command);
         msg->cmd[strlen(command)-1] = 0;  /* wipe out the EOL character */
         }
      }

   /*  Send the message with instructions on actions to SAS/C Help's port  */
   PutMsg (port,(struct Message *) msg);

   /*  If this is the first time this program is called, set up the input
       handler, etc.   */
   if (!first_call)
      clean_exit(inputRequestBlock, handlerStuff, global, inputDevPort,
                nullfh1, nullfh2, port, first_call);

   /* Close _Backstdout so that the original window can go away */
   if (_Backstdout)
      Close(_Backstdout);

   _Backstdout = 0;


   /* set the input and output streams to 0 so execute doen't complain */
   nullfh1 = Open("NIL:", MODE_NEWFILE);
   nullfh2 = Open("NIL:", MODE_NEWFILE);

   if (((inputDevPort = CreatePort(0,0)) == NULL)  ||

      ((inputRequestBlock =
          CreateIOReq(inputDevPort, sizeof(struct IOStdReq))) == NULL)  ||

      ((global.helpsignum = AllocSignal(-1)) == -1)  ||

      OpenDevice("input.device",0,(struct IORequest *)inputRequestBlock,0))

      clean_exit(inputRequestBlock, handlerStuff, global, inputDevPort,
                nullfh1, nullfh2, port, first_call);

   /*  Set up stuff for input handler  */
   handlerStuff.is_Data = (APTR) &global;
   handlerStuff.is_Code = (vfunc) HandlerInterface;
   handlerStuff.is_Node.ln_Pri = 51;

   /*  Set up global data to be used by input handler  */
   global.buddy = FindTask(0);
   global.helpsig  = 1 << global.helpsignum;
   global.key = DEFKEY;

   inputRequestBlock->io_Command = IND_ADDHANDLER;
   inputRequestBlock->io_Data    = (APTR)&handlerStuff;

   DoIO((struct IORequest *)inputRequestBlock);

   for(;;)         /* FOREVER */
      {
      sig = Wait( global.helpsig | (1 << port->mp_SigBit) );

      /* Check contents of message and quit or change the command to be
         executed                                                     */
      if (sig & (1 << port->mp_SigBit))
         {
         while ((msg = (struct OURMSG *)GetMsg(port)) != NULL)
            {
            tmpmsg = *msg;
            ReplyMsg ( (struct Message *) msg);
            if (tmpmsg.quit)
               clean_exit(inputRequestBlock, handlerStuff, global,
                          inputDevPort, nullfh1, nullfh2, port, first_call);
            if (tmpmsg.cmd[0])
               strcpy(cmdstr, msg->cmd);
            }
         }

      /*  Execute the command  */
      if (sig & global.helpsig)
         {
         (void)Execute(cmdstr,nullfh1,nullfh2);
         }
      }
}

/********************************************************************/

/*  Clean up and exit */
void clean_exit(struct IOStdReq *inputRequestBlock, struct Interrupt
     handlerStuff, GLOBAL_DATA global, struct MsgPort *inputDevPort,
     BPTR nullfh1, BPTR nullfh2, struct MsgPort * port, int first_call)
{
   if(_Backstdout)
      Close(_Backstdout);

   if (inputRequestBlock != NULL)
      {
      if (inputRequestBlock->io_Device != NULL)
         {
         inputRequestBlock->io_Command = IND_REMHANDLER;
         inputRequestBlock->io_Data = (APTR)&handlerStuff;
         DoIO((struct IORequest *)inputRequestBlock);

         CloseDevice((struct IORequest *)inputRequestBlock);
         }
      DeleteIOReq(inputRequestBlock);
      }
   if (global.helpsignum != -1)   
       FreeSignal(global.helpsignum);
   if (inputDevPort != NULL)       
       DeletePort(inputDevPort);
   if (first_call && (port != NULL)) 
       DeletePort(port);
   if (nullfh1)  
       Close(nullfh1);
   if (nullfh2)  
       Close(nullfh2);

   exit(0);
}



/************************************************************************/

/* This routine is the input handler function.  It is put into the input
   handler list at a priority above Intuition's so that it can check
   all input for the SAS/C Help key sequence.                           */

/* Note the use of the __interrupt keyword below.  This is used to   */
/* prohibit the generation of stack-checking or stack-extension code */
/* if the module is compiled with the STACKCHECK or STACKEXT options.*/
/* (STACKCHECK is the default).  Because this function is called     */
/* directly from input.device, it will be using input.device's stack */
/* and not the program's stack; hence code that relies on using the  */
/* program's stack must be disabled.                                 */

struct InputEvent * __interrupt __asm HandlerInterface(
                     register __a0 struct InputEvent *event,
                     register __a1 GLOBAL_DATA *gptr)
{
   register struct InputEvent *cur_event, *last_event;

   /* Step through the event list, checking for SAS/C Help key sequence  */
   for (cur_event = event, last_event = NULL; cur_event != NULL;
                   cur_event = cur_event->ie_NextEvent)
      {
      if ((cur_event->ie_Class == IECLASS_RAWKEY)    &&
          (cur_event->ie_Code  == gptr->key)         &&
          (cur_event->ie_Qualifier & IEQUALIFIER_CONTROL))
         {
         /*  This event was our key sequence, so take it off of the event chain */
         if (last_event == NULL)
            event = cur_event->ie_NextEvent;
         else
            event = last_event->ie_NextEvent = cur_event->ie_NextEvent;

         /* Notify the SAS/C Help task to pop up the help window */
         Signal(gptr->buddy, gptr->helpsig);
         }
      else
         last_event = cur_event;

      }
   /* Return the pointer to the event */
   return(event);
}


/************************************************************************/


struct IOStdReq *CreateIOReq(struct MsgPort *port, int size)
{
   struct IOStdReq *ioReq;

   if ((ioReq = (struct IOStdReq *)
                AllocMem(size, MEMF_CLEAR | MEMF_PUBLIC)) != NULL)
      {
      ioReq->io_Message.mn_Node.ln_Type = NT_MESSAGE;
      ioReq->io_Message.mn_Node.ln_Pri  = 0;
      ioReq->io_Message.mn_Length       = size;
      ioReq->io_Message.mn_ReplyPort    = port;
      }
   return(ioReq);
}


/************************************************************************/



void DeleteIOReq (struct IOStdReq *ioReq)
{
   ioReq->io_Message.mn_Node.ln_Type = 0xff;
   ioReq->io_Device = (struct Device *) -1;
   ioReq->io_Unit = (struct Unit *) -1;

   FreeMem( (char *)ioReq, ioReq->io_Message.mn_Length);
}
