/************************************************************************
 *  a terminal program that has ascii and xmodem transfer capability
 *
 *  use esc to abort xmodem transfer
 *
 *  written by Michael Mounier
 *
 *  Modification History -
 *   December 85 - added 80 * 25 capabilities  - Larry Phillips
 *   Feb 9 1986  - added Half-duplex and Echoplex
 *               - added timing to and fixed bugs in XMODEM receive
 *               - fixed bug in Ascii send -- now doesn't wait for EVENT
 *                 to send next characters - Steve Allen
 ************************************************************************/
 
 #define TESTING 1
 /*  compiler directives to fetch the necessary header files */
 #include <exec/types.h>
 #include <exec/exec.h>
 #include <intuition/intuition.h>
 #include <intuition/intuitionbase.h>
 #include <graphics/gfxbase.h>
 #include <graphics/gfx.h>
 #include <graphics/text.h>
 #include <graphics/regions.h>
 #include <graphics/copper.h>
 #include <graphics/gels.h>
 #include <devices/serial.h>
 #include <devices/keymap.h>
 #include <hardware/blit.h>
 #include <stdio.h>
 #include <ctype.h>
 #include <libraries/dos.h>
 
 #define INTUITION_REV 1
 #define GRAPHICS_REV  1
 
 /* things for xmodem send and receive */
 #define SECSIZ   128             /* This many bytes in an xmodem block */
 #define BufSize  SECSIZ*2  /* We'll follow two blocks (for chop) */
 #define ERRORMAX 10        /* Max errors before abort */
 #define RETRYMAX 10       /* Maximum retrys before abort */
 #define SOH      1       /* Start of sector char */
 #define EOT      4      /* end of transmission char */
 #define ACK      6     /* acknowledge sector transmission */
 #define NAK      21   /* error in transmission detected */
 #define CAN      'X'&0x1f
 #define CR       13
 #define BELL    'G'&0x1f
 #define CPM_EOF 'Z'&0x1f    /*CP/M end of file character */
 static char
     bufr[BufSize], numstring[5];
 static int
     fd,
     timeout = FALSE,
     cancel,
     LOCAL, ECHO;
 static long
     bytes_xferred;
 
 UBYTE ModemRead();  /* C demands this non-integer declaration */
                     /* 'cause ModemRead is used before it is defined */
 
 /*   Intuition always wants to see these declarations */
 struct IntuitionBase *IntuitionBase;
 struct GfxBase *GfxBase;
 
 /* my window structure */
 struct NewWindow NewWindow = {
    0,
    0,
    640,
    200,
    0,
    1,
    CLOSEWINDOW | RAWKEY | MENUPICK | NEWSIZE,
    WINDOWCLOSE | SMART_REFRESH | ACTIVATE | WINDOWDEPTH | REPORTMOUSE
    | BORDERLESS,
    NULL,
    NULL,
    "AMIGA Terminal",
    NULL,
    NULL,
    100, 35,
    640, 200,
    WBENCHSCREEN,
 };
 
 struct Window *mywindow;             /* ptr to applications window */
 struct IntuiMessage *NewMessage;    /* msg structure for GetMsg() */
 
 /*****************************************************
 *                     File Menu
 *****************************************************/
 
 /* define maximum number of menu items */
 #define FILEMAX 4
 
 /*   declare storage space for menu items and
  *   their associated IntuiText structures
  */
 struct MenuItem FileItem[FILEMAX];
 struct IntuiText FileText[FILEMAX];
 
 /*****************************************************************/
 /*    The following function initializes the structure arrays    */
 /*   needed to provide the File menu topic.                      */
 /*****************************************************************/
 InitFileItems()
 {
 short n;
 
 /* initialize each menu item and IntuiText with loop */
 for( n=0; n<FILEMAX; n++ )
    {
    FileItem[n].NextItem = &FileItem[n]+1;
    FileItem[n].LeftEdge = 0;
    FileItem[n].TopEdge = 11 * n;
    FileItem[n].Width = 135;
    FileItem[n].Height = 11;
    FileItem[n].Flags = ITEMTEXT | ITEMENABLED | HIGHBOX;
    FileItem[n].MutualExclude = 0;
    FileItem[n].ItemFill = (APTR)&FileText[n];
    FileItem[n].SelectFill = NULL;
    FileItem[n].Command = 0;
    FileItem[n].SubItem = NULL;
    FileItem[n].NextSelect = 0;
 
    FileText[n].FrontPen = 0;
    FileText[n].BackPen = 1;
    FileText[n].DrawMode = JAM2;     /* render in fore and background */
    FileText[n].LeftEdge = 0;
    FileText[n].TopEdge = 1;
    FileText[n].ITextFont = NULL;
    FileText[n].NextText = NULL;
    }
 FileItem[FILEMAX-1].NextItem = NULL;
 
 /* initialize text for specific menu items */
 FileText[0].IText = (UBYTE *)"Ascii Capture";
 FileText[1].IText = (UBYTE *)"Ascii Send";
 FileText[2].IText = (UBYTE *)"Xmodem Receive";
 FileText[3].IText = (UBYTE *)"Xmodem Send";
 
 return( 0 );
 }
 
 /*****************************************************/
 /*                BaudRate  Menu                     */
 /*****************************************************/
 
 /* define maximum number of menu items */
 #define RSMAX 5
 
 /*   declare storage space for menu items and
  *   their associated IntuiText structures
  */
 struct MenuItem RSItem[RSMAX];
 struct IntuiText RSText[RSMAX];
 
 /*****************************************************************/
 /*    The following function initializes the structure arrays    */
 /*   needed to provide the BaudRate menu topic.                  */
 /*****************************************************************/
 InitRSItems()
 {
 short n;
 
 /* initialize each menu item and IntuiText with loop */
 for( n=0; n<RSMAX; n++ )
    {
    RSItem[n].NextItem = &RSItem[n]+1;
    RSItem[n].LeftEdge = 0;
    RSItem[n].TopEdge = 11 * n;
    RSItem[n].Width = 85;
    RSItem[n].Height = 11;
    RSItem[n].Flags = ITEMTEXT | ITEMENABLED | HIGHBOX | CHECKIT;
    RSItem[n].MutualExclude = (~(1 << n));
    RSItem[n].ItemFill = (APTR)&RSText[n];
    RSItem[n].SelectFill = NULL;
    RSItem[n].Command = 0;
    RSItem[n].SubItem = NULL;
    RSItem[n].NextSelect = 0;
 
    RSText[n].FrontPen = 0;
    RSText[n].BackPen = 1;
    RSText[n].DrawMode = JAM2;     /* render in fore and background */
    RSText[n].LeftEdge = 0;
    RSText[n].TopEdge = 1;
    RSText[n].ITextFont = NULL;
    RSText[n].NextText = NULL;
    }
 RSItem[RSMAX-1].NextItem = NULL;
 /* 300 baud item chekced */
 RSItem[0].Flags = ITEMTEXT | ITEMENABLED | HIGHBOX | CHECKIT | CHECKED;
 
 /* initialize text for specific menu items */
 RSText[0].IText = (UBYTE *)"   300";
 RSText[1].IText = (UBYTE *)"   1200";
 RSText[2].IText = (UBYTE *)"   2400";
 RSText[3].IText = (UBYTE *)"   4800";
 RSText[4].IText = (UBYTE *)"   9600";
 
 return( 0 );
 }
 
 /***************************************************
 *                 Mode menu         (feb.86)
 ****************************************************/
 struct MenuItem ModeItem[3];
 struct IntuiText Modetext[3];
 
 initmode()
 {
 short n;
 
 for ( n=0; n<3; n++)
 
 {
  ModeItem[n].NextItem = &ModeItem[n]+1; /* Lattice 3.03 likes this syntax */
  ModeItem[n].LeftEdge = 0;
  ModeItem[n].TopEdge = 11 * n;
  ModeItem[n].Width = 50;
  ModeItem[n].Height = 11;
  ModeItem[n].Flags = ITEMTEXT | ITEMENABLED | HIGHBOX | CHECKIT;
  ModeItem[n].MutualExclude = (~(1 << n));
  ModeItem[n].ItemFill = (APTR) &Modetext[n];
  ModeItem[n].SelectFill = NULL;
  ModeItem[n].Command = 0;
  ModeItem[n].SubItem = NULL;
  ModeItem[n].NextSelect = 0;
 
  Modetext[n].FrontPen = 0;
  Modetext[n].BackPen = 1;
  Modetext[n].DrawMode = JAM2;
  Modetext[n].LeftEdge = 0;
  Modetext[n].TopEdge = 1;
  Modetext[n].ITextFont = NULL;
  Modetext[n].NextText = NULL;
 }
 
 ModeItem[2].NextItem = NULL;
 
 /* default is Terminal mode  (full duplex)  */
 ModeItem[1].Flags = ITEMTEXT | ITEMENABLED | HIGHBOX | CHECKIT | CHECKED;
 LOCAL = 0;
 ECHO = 0;
 
 Modetext[0].IText = "    Half";
 Modetext[1].IText = "    Full";
 Modetext[2].IText = "    Echo";
 
 }     /* end initmode */
 
 
 /***************************************************/
 /*                Menu Definition                  */
 /*                                                 */
 /*      This section of code is where the simple   */
 /*   menu definition goes.                         */
 /***************************************************/
 
 /* current number of available menu topics */
 #define MAXMENU 3
 
 /*   declaration of menu structure array for
  *   number of current topics.  Intuition
  *   will use the address of this array to
  *   set and clear the menus associated with
  *   the window.
  */
 struct Menu menu[MAXMENU];
 
 /**********************************************************************/
 /*   The following function initializes the Menu structure array with */
 /*  appropriate values for our simple menu strip.  Review the manual  */
 /*  if you need to know what each value means.                        */
 /**********************************************************************/
 InitMenu()
 {
 menu[0].NextMenu = &menu[1];
 menu[0].LeftEdge = 5;
 menu[0].TopEdge = 0;
 menu[0].Width = 50;
 menu[0].Height = 10;
 menu[0].Flags = MENUENABLED;
 menu[0].MenuName = "File";           /* text for menu-bar display */
 menu[0].FirstItem = &FileItem[0];    /* pointer to first item in list */
 
 menu[1].NextMenu = &menu[2];
 menu[1].LeftEdge = 65;
 menu[1].TopEdge = 0;
 menu[1].Width = 85;
 menu[1].Height = 10;
 menu[1].Flags = MENUENABLED;
 menu[1].MenuName = "BaudRate";        /* text for menu-bar display */
 menu[1].FirstItem = &RSItem[0];    /* pointer to first item in list */
 
 menu[2].NextMenu = NULL;                   /* (feb.86) */
 menu[2].LeftEdge = 150;
 menu[2].TopEdge = 0;
 menu[2].Width = 40;
 menu[2].Height = 10;
 menu[2].Flags = MENUENABLED;
 menu[2].MenuName = "Mode";
 menu[2].FirstItem = &ModeItem[0];
 }
 
 /* declarations for the serial stuff */
 extern struct MsgPort *CreatePort();
 struct IOExtSer *Read_Request;
 static char rs_in[2];
 struct IOExtSer *Write_Request;
 static char rs_out[2];
 
 /******************************************************/
 /*                   Main Program                     */
 /*                                                    */
 /*      This is the main body of the program.         */
 /******************************************************/
 
 main()
 {
 ULONG class;
 USHORT code,menunum,itemnum;
 int KeepGoing,capture,send;
 char c,name[32];
 FILE *tranr,*trans;
 
 IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", INTUITION_REV);
 if( IntuitionBase == NULL )
    {
    puts("can't open intuition\n");
    exit(TRUE);
    }
 
 GfxBase = (struct GfxBase *)OpenLibrary("graphics.library",GRAPHICS_REV);
 if( GfxBase == NULL )
    {
    puts("can't open graphics library\n");
    exit(TRUE);
    }
 
 if(( mywindow = (struct Window *)OpenWindow(&NewWindow) ) == NULL)
    {
    puts("cant open window\n");
    exit(TRUE);
    }
 
 Read_Request = (struct IOExtSer *)AllocMem(sizeof(*Read_Request),MEMF_PUBLIC|MEMF_CLEAR);
 Read_Request->io_SerFlags = SERF_SHARED;
 Read_Request->io_CtlChar = 0x11130501;
 Read_Request->IOSer.io_Message.mn_ReplyPort = CreatePort("Read_RS",0);
 if(OpenDevice(SERIALNAME,NULL,Read_Request,NULL))
    {
    puts("Cant open Read device\n");
    CloseWindow( mywindow );
    DeletePort(Read_Request->IOSer.io_Message.mn_ReplyPort);
    FreeMem(Read_Request,sizeof(*Read_Request));
    exit(TRUE);
    }
 Read_Request->IOSer.io_Command = CMD_READ;
 Read_Request->IOSer.io_Length = 1;
 Read_Request->IOSer.io_Data = (APTR) &rs_in[0];
 
 Write_Request = (struct IOExtSer *)AllocMem(sizeof(*Write_Request),MEMF_PUBLIC|MEMF_CLEAR);
 Write_Request->io_SerFlags = SERF_SHARED;
 Write_Request->io_CtlChar = 0x11130501;
 Write_Request->IOSer.io_Message.mn_ReplyPort = CreatePort("Write_RS",0);
 if(OpenDevice(SERIALNAME,NULL,Write_Request,NULL))
    {
    puts("Cant open Write device\n");
    CloseWindow( mywindow );
    DeletePort(Write_Request->IOSer.io_Message.mn_ReplyPort);
    FreeMem(Write_Request,sizeof(*Write_Request));
    DeletePort(Read_Request->IOSer.io_Message.mn_ReplyPort);
    FreeMem(Read_Request,sizeof(*Read_Request));
    exit(TRUE);
    }
 Write_Request->IOSer.io_Command = CMD_WRITE;
 Write_Request->IOSer.io_Length = 1;
 Write_Request->IOSer.io_Data = (APTR) &rs_out[0];
 
 Read_Request->io_SerFlags = SERF_SHARED | SERF_XDISABLED;
 Read_Request->io_Baud = 300;
 Read_Request->io_ReadLen = 8;
 Read_Request->io_WriteLen = 8;
 Read_Request->io_CtlChar = 1L;
 Read_Request->IOSer.io_Command = SDCMD_SETPARAMS;
 DoIO(Read_Request);
 Read_Request->IOSer.io_Command = CMD_READ;
 
 InitFileItems();
 InitRSItems();
 initmode();
 InitMenu();
 SetMenuStrip(mywindow,&menu[0]);
 
 KeepGoing = TRUE;
 capture=FALSE;
 send=FALSE;
 SetAPen(mywindow->RPort,1);
 emit(12);
 
 BeginIO(Read_Request);
 
 while( KeepGoing )
      {
                      /* wait for window message or serial port message */
      if (send == 0)           /* don't wait for event when sending */
      Wait((1 << Read_Request->IOSer.io_Message.mn_ReplyPort->mp_SigBit) | ( 1 << mywindow->UserPort->mp_SigBit));
 
      if (send)
          {
          if ((c=getc(trans)) != EOF)
              {
              if (c == '\n')  sendchar(CR);  /* add carriage return  */
              sendchar(c);
              }
          else
              {
              fclose(trans);
              emits("\nFile Sent\n");
              send=FALSE;
              }
          }
      if(CheckIO(Read_Request))
        {
         WaitIO(Read_Request);
         c=rs_in[0] & 0x7f;
         BeginIO(Read_Request);
         emit(c);
         if (ECHO)     /* simple echo, no LF after CR   (feb.86) */
          {
           rs_out[0] = c;       /* must do this way: sendchar() also */
           DoIO(Write_Request); /* emit()s if LOCAL, and calling sendchar() */
           }                    /* would do a double-emit() in EchoPlex */
         if (capture)
             if (c > 31 && c < 127 || c == '\n') /* assumes CR/LF sequence */
                                    /* trash them mangy ctl chars */
                 putc(c , tranr);
         }
 
      while( NewMessage=(struct IntuiMessage *)GetMsg(mywindow->UserPort) )
           {
           class = NewMessage->Class;
           code = NewMessage->Code;
           ReplyMsg( NewMessage );
           switch( class )
                 {
                 case CLOSEWINDOW:
                    /*   User is ready to quit, so indicate
                    *   that execution should terminate
                    *   with next iteration of the loop.
                    */
                    KeepGoing = FALSE;
                 break;
 
                 case RAWKEY:
                 /*  User has touched the keyboard */
                 switch( code )
                       {
                       case 95: /* help key */
                          emits("ESC Aborts Xmodem Xfer\n");
                          emits("Front/Back gadgets and Close gadget are still active though usneen.\n");
                          emits("AMIGA Term 1.0\n");
                          emits("Copyright 1985 by Michael Mounier\n");
                       break;
                       default:   
                          c = toasc(code); /* get in into ascii */
                          if (c != 0)
                             {
                              rs_out[0] = c;
                              DoIO(Write_Request);
                              if (LOCAL) emit(c);
                              }
                       break;
                       }
                 break;
 
                 case NEWSIZE:
                    emit(12);
                 break;
 
                 case MENUPICK:
                    if ( code != MENUNULL )
                        {
                        menunum = MENUNUM( code );
                        itemnum = ITEMNUM( code );
                        switch( menunum )
                              {
                              case 0:
                                 switch( itemnum )
                                       {
                                       case 0:
                                          if (capture == TRUE)
                                              {
                                              capture=FALSE;
                                              fclose(tranr);
                                              emits("\nEnd File Capture\n");
                                              }
                                          else
                                              {
                                              emits("\nAscii Capture:");
                                              filename(name);
                                              if ((tranr=fopen(name,"w")) == 0)
                                                  {
                                                  capture=FALSE;
                                                  emits("\nError Opening File\n");
                                                  break;
                                                  }
                                              capture=TRUE;
                                              }
                                       break;
                                       case 1:
                                          if (send == TRUE)
                                              { 
                                              send=FALSE;
                                              fclose(trans);
                                              emits("\nFile Send Cancelled\n");
                                              }
                                          else
                                              {
                                              emits("\nAscii Send:");
                                              filename(name);
                                              if ((trans=fopen(name,"r")) == 0)
                                                  {
                                                  send=FALSE;
                                                  emits("\nError Opening File\n");
                                                  break;
                                                  }
                                              send=TRUE;
                                              }
                                       break;
                                       case 2:
                                          emits("\nXmodem Receive:");
                                          filename(name);
                                          if (XMODEM_Read_File(name))
                                             {
                                             emits("\nReceived File\n");
                                             emit(BELL);
                                             }
                                          else
                                             {
                                             close(fd);
                                             emits("Xmodem Receive Failed\n");
                                             emit(BELL);
                                             }
                                       break;
                                       case 3:
                                          emits("\nXmodem Send:");
                                          filename(name);
                                          if (XMODEM_Send_File(name))
                                              {
                                              emits("\nSent File\n");
                                              emit(BELL);
                                              }
                                          else
                                              {
                                              close(fd);
                                              emits("\nXmodem Send Failed\n");
                                              emit(BELL);
                                              }
                                       break;
                                       }
                              break;
 
                              case 1:
                                 AbortIO(Read_Request);
                                 switch( itemnum )
                                       {
                                       case 0:
                                          Read_Request->io_Baud = 300;
                                       break;
                                       case 1:
                                          Read_Request->io_Baud = 1200;
                                       break;
                                       case 2:
                                          Read_Request->io_Baud = 2400;
                                       break;
                                       case 3:
                                          Read_Request->io_Baud = 4800;
                                       break;
                                       case 4:
                                          Read_Request->io_Baud = 9600;
                                       break;
                                       }
                                 Read_Request->IOSer.io_Command = SDCMD_SETPARAMS;
                                 DoIO(Read_Request);
                                 Read_Request->IOSer.io_Command = CMD_READ;
                                 BeginIO(Read_Request);
                              break;
                             case 2:                /*   "Mode" (feb.86) */
                               switch( itemnum)
                               {
                                case 0:  /* Half Duplex */
                                 ECHO = 0;
                                 LOCAL = 1;
                                 break;
                                case 1:  /* Full Duplex */
                                 ECHO = 0;
                                 LOCAL = 0;
                                 break;
                                case 2:  /* EchoPlex  */
                                 ECHO = 1;
                                 LOCAL = 1;
                                }
                              } /* end of switch ( menunum ) */
                        }    /*  end of if ( not null ) */
                 }   /* end of switch (class) */
           }   /* end of while ( newmessage )*/
      }  /* end while ( keepgoing ) */
 
 /*   It must be time to quit, so we have to clean
 *   up and exit.
 */
        
 CloseDevice(Read_Request);
 DeletePort(Read_Request->IOSer.io_Message.mn_ReplyPort);
 FreeMem(Read_Request,sizeof(*Read_Request));
 CloseDevice(Write_Request);
 DeletePort(Write_Request->IOSer.io_Message.mn_ReplyPort);
 FreeMem(Write_Request,sizeof(*Write_Request));
 ClearMenuStrip( mywindow );
 CloseWindow( mywindow );
 exit(FALSE);
 } /* end of main */
 
 /*************************************************
 *  function to get file name
 *************************************************/
 
 filename(name)
 char name[];
 {
 char c;
 ULONG class;
 USHORT code;
 int keepgoing,i;
 keepgoing = TRUE;
 i=0;
 while (keepgoing) {
       while( NewMessage=(struct IntuiMessage *)GetMsg(mywindow->UserPort) )
            {
            class = NewMessage->Class;
            code = NewMessage->Code;
            ReplyMsg( NewMessage );
            if (class=RAWKEY)
               {
               c = toasc(code);
               name[i]=c;
               if (name[i] != 0)
                  {
                  if (name[i] == 13)
                     {
                     name[i]=0;
                     keepgoing = FALSE;
                     }
                  else
                     {
                     if (name[i] == 8)
                        {
                        i -=2;
                        if (i < -1)
                           i = -1;
                        else
                           {
                           emit(8);
                           emit(32);
                           emit(8);
                           }
                        }
                     else
                        emit(c);
                     }
                  i += 1;
                  }
               }
           } /* end of new message loop */
       }   /* end of god knows what */
       emit(13);
 } /* end of function */
 
 
 /*************************************************
 *  function to print a string
 *************************************************/
 emits(string)
 char string[];
 {
 int i;
 char c;
 
 i=0;
 while (string[i] != 0)
       {
       c=string[i];
       if (c == 10)
          c = 13;
       emit(c);
       i += 1;
       }
 }
 
 sendchar(ch)
   int ch;
   {
     rs_out[0] = ch;
     DoIO(Write_Request);
     if (LOCAL)  emit(ch);  /* if Half-Duplex or Echo-plex  (feb.86)  */
    }
 
 /**************************************************************/
 /* send char and read char functions for the xmodem function */
 /************************************************************/
 
 /*    purge() is called whenever an error is detected.
      Its function is to flush the input buffer, and wait
      until the sender has finished sending a block.
      This ensures that the sender will see the NAK
      sent by the error routine.
                (feb.86)
 */
 
 void purge(purgetime)
     int purgetime;      /* number of ticks @ 50 per second */
    {
 
     do  ModemRead(purgetime);
     while (timeout == FALSE);       /* Wait for the line to clear */
    }
 
 
 
      /* ModemSend() is simply sendchar() without local echo
         (feb.86)   */
 
 void ModemSend(c)
     UBYTE c;
     { rs_out[0] = c;
       DoIO(Write_Request);
      }
 
 /*     ModemRead()  gets a character from the serial port within a
       specified number of ticks (tick = 1/50 second). DateStamp()
       returns a 3-byte integer date consisting of day, minute, ticks.
       ModemRead() will function correctly as long as the date is set
       and a day boundary isn't crossed.
          (feb.86')
 */
 
 
 UBYTE ModemRead(MAXTICKS)
    int MAXTICKS;              /* number of ticks @ 50 per second */
   {
    static int startime[3], endtime[3];
    UBYTE c;
  
    timeout = FALSE;
    DateStamp(startime);
    do {
        if (NewMessage = (struct IntuiMessage *)GetMsg(mywindow->UserPort))
         { 
           if (NewMessage->Class == RAWKEY)  /* test for user cancelling */
            {
             if ((NewMessage->Code) == 69)     /* escape key pressed */
              { cancel = TRUE;
                return (0);  /* This will tend to force an error        */
               }             /* when <esc> pressed 'midst block receive */
             }
          }
        if (CheckIO(Read_Request))
         { WaitIO(Read_Request);
           c = rs_in[0];
           BeginIO(Read_Request);
           return c;             /* Character came in on time */
          }
        DateStamp(endtime);
        endtime[2] -= startime[2];
         if ( endtime[1] -= startime[1] ) /* did minutes count change? */
         endtime[2] += endtime[1] * 3000; /* number of ticks in minute */
       }
    while (endtime[2] < MAXTICKS);
      /* else character not received on time */
    timeout = TRUE;
    return (0);
   }
 
 
 
 /*   CloseEmUp() closes a receive file, ending Xmodem receive.
      It also performs a 'chop' function, testing for
      zero, 0xff, and CP/M_EOF (ctrol-z).
      To implement the chop function, a two-block buffer
      is maintained, with the basic pointer being bufr[offset],
      with offset toggled 0,SECSIZ,0, etc.
      During file receive, writes lag reads by one block: e.g.,
      when block # 3 is received, block # 2 is written.
      If block # 3 is the final block, CloseEmUp() will
      chop any invalid characters, write the block, and
      close the file.          (feb.86)
 */
 
 CloseEmUp(fd,bufr,offset,ourblock)
 
 int offset, ourblock;
 UBYTE *bufr;
 
 {
  int i;
  UBYTE c;
 
  offset = offset ? 0 : SECSIZ ;  /* toggle offset */
 
  if (ourblock)                     /* skip if no data at all */
   {
     i = SECSIZ - 1;
     c = bufr[offset + i];       /* c gets last char in block */
     if ( (c == 0) || (c == CPM_EOF) || (c == 0xff) )
       {
         while (bufr[ offset+(--i) ] == c)  /* Chop those useless bytes! */
                    ;
        }
     if ((c = write(fd, bufr+offset, ++i)) != i)
        emits("Error writing final block\n");
     stcu_d(numstring, SECSIZ-i, 4);
     emits(numstring);
     emits(" bytes chopped from final block\n");
    }
 
  close(fd);
 }
 
 
 /**************************************/
 /* xmodem send and receive functions */
 /************************************/
 
             /* These time constants are for benifit of
                CompuServe and other packet-switching networks
                that must be allowed more "slack time"
                due to inherent delays in system.
              */
 
 #define Blocktime 20*50 /* Wait 20 seconds for start of block */
 #define Chartime   2*50 /* Wait 2 seconds for chars when getting block */
 
 
 XMODEM_Read_File(file)                /* (feb.86) */
 
 char *file;
 
 {
 extern int timeout;
 int blockin, ourblock, errors, checksum, offset, x,i; /*NEWCODE*/
 unsigned char c;
 
 if ( (fd = creat(file,0)) < 0)
    { emits("Cannot open file\n");
      return FALSE;
     }
 else emits("Receiving file \n");
 
          /* Input file now open */
 
 offset = ourblock = errors = cancel = 0;
 
 ModemSend(NAK);
                         /*  MAIN LOOP STARTS HERE  */
 loop:
 
 c = ModemRead(Blocktime);
 
    if (cancel)
    { emits("Xmodem cancelled\n");
      close(fd);
      purge(Chartime);
      ModemSend(CAN);
      return(FALSE);
     }
 
    if (timeout)
    { emits("timed out waiting for block start\n");
      goto error;
     }
 
 switch (c)
  {
    case (CAN):    /*Sender just quit*/
    { purge(Chartime);
      emits("Sender aborted transfer\n");
      CloseEmUp(fd,bufr,offset,ourblock);
      return(FALSE);
     }
    case (EOT):    /*Sender is finished */
    {
      CloseEmUp(fd,bufr,offset,ourblock);   /* chop and write last block */
      emits("Transfer Completed\n");
      ModemSend(ACK);
      return(TRUE);
     }
    case (! SOH):
    { purge(Chartime);
      emits("not a valid header\n");
      goto error;
     }
    default:   /*Sender has a block for us */
    break;    /* block receive follows here */
   }
 blockin = ModemRead(Chartime);  /* get block number */
    if (timeout)
    { purge(Chartime);
      emits("Didn't get block# in time\n");
      goto error;
     }
 c = ModemRead(Chartime);       /* get 1/blocknumber */
    if (timeout)
    { purge(Chartime);
      emits("Timed out waiting for 1/block#\n");
      goto error;
     }
 
 if ((c + blockin) != 255)  /* block # got lunched */
    { purge(Chartime);
      emits("block complement didn't match\n");
      goto error;
     }
             /* Here comes a block of data  */
 
 checksum = 0;
 
 for (i = 0; i < SECSIZ; i++ )
  {
    c = ModemRead(Chartime);
      if (timeout)
       { purge(Chartime);
         emits("Timed out during block\n");
         goto error;
        }
    bufr[offset+i] = c;
    checksum += c;
   }
 
             /* Here comes the checksum */
 c = ModemRead(Chartime);
    if (timeout)
    { purge(Chartime);
      emits("Timed out waiting for checksum\n");
      goto error;
     }
 if (c != (checksum & 0xff) ) /* checksums don't match */
   {
    purge(Chartime);
    emits("checksum error\n");
    goto error;
    }
 
       /* Block has been received OK: Now check back on blocknumber */
 
 switch ( (char) (blockin - (ourblock & 0xff)) )
 {
  case (0):       /* Duplicate block */
   {
    emits("\nDuplicate block #  ");
    stcu_d(numstring,ourblock,4);
    emits(numstring);
    emit( '\r' );
    errors = 0;
    ModemSend(ACK);
    goto loop;
   }
  case (1):      /* New block */
   {
    ourblock++;           /* Update our block count */
    emits("\vReceived block # ");
    stcu_d(numstring,ourblock,4);
    emits(numstring);
    emits("     \n" );
 
    offset = offset ? 0 : SECSIZ;                  /* toggle offset */
    if (ourblock != 1)
    {                                              /* Write it out */
      if ( (x = (write(fd, bufr+offset, SECSIZ)) ) != SECSIZ)
        { close(fd);
          emits("Error writing to file.\n");
          emits("Transfer aborted.\n");
          ModemSend(CAN);
          return(FALSE);
         }
     }
    errors = 0;
    ModemSend(ACK);
    goto loop;
   }
 default:
     break;     /* fall through to error: */
 }
 purge(Chartime);
 emits("Block numbers didn't agree\n");
 
                   /* ERROR ROUTINE STARTS HERE */
 error:
 
 if (cancel)
    { emits("XModem cancelled\n");
      close(fd);
      ModemSend(CAN);
      return(FALSE);
     }
 
  errors++;
 
  if (errors == 10) /* then abort */
    {
      emits(" 10 Errors. Transfer cancelled.\n");
      close(fd);
      ModemSend(CAN);
      return(FALSE);
     }
  emits("Error # ");
  emit( errors + '0');
  emit( '\r' );
  ModemSend(NAK);
  goto loop;
 
 } /* end of XMODEM_Read_File()  */
 
 
 #define WaitTime 20*50  /* Wait this long for NAKs (20 seconds)*/
 
 XMODEM_Send_File(file)
     char *file;
 {
     int sectnum, bytes_to_send, size, attempts, c, i;
     unsigned checksum, j, bufptr;
     char numb[10];
 
     timeout=FALSE;
     bytes_xferred = 0;
     if ((fd = open(file, 1)) < 0)
         {
         emits("Cannot Open Send File\n");
         return FALSE;
         }
     else
         emits("Sending File\n");
     attempts = 0;
     sectnum = 1;
 /* wait for sync char */
     j=1;
     while (((c = ModemRead()) != NAK) && (j++ < ERRORMAX));
     if (j >= (ERRORMAX))
         {
         emits("\nReceiver not sending NAKs\n");
         return FALSE;
         };
 
     while ((bytes_to_send = read(fd, bufr, BufSize)) && attempts != RETRYMAX)
         {
         if (bytes_to_send == EOF)
             {
             emits("\nError Reading File\n");
             return FALSE;
             };
 
         bufptr = 0;
         while (bytes_to_send > 0 && attempts != RETRYMAX)
             {
             attempts = 0;
             do
                 {
                 ModemSend(SOH);
                 ModemSend(sectnum);
                 ModemSend(~sectnum);
                 checksum = 0;
                 size = SECSIZ <= bytes_to_send ? SECSIZ : bytes_to_send;
                 bytes_to_send -= size;
                 for (j = bufptr; j < (bufptr + SECSIZ); j++)
                     if (j < (bufptr + size))
                         {
                         ModemSend(bufr[j]);
                         checksum += bufr[j];
                         }
                     else
                         {
                         ModemSend(0);
                         }
                 ModemSend(checksum & 0xff);
                 attempts++;
                 c = ModemRead(WaitTime);
                 if (timeout == TRUE)
                    return FALSE;
                 }
             while ((c != ACK) && (attempts != RETRYMAX));
             bufptr += size;
             bytes_xferred += size;
             emits("Block ");
             stci_d(numb,sectnum,i);
             emits(numb);
             emits(" sent\n");
             sectnum++;
             }
         }
     close(fd);
     if (attempts == RETRYMAX)
         {
         emits("\nNo Acknowledgment Of Sector, Aborting\n");
         return FALSE;
         }
     else
         {
         attempts = 0;
         do
             {
             ModemSend(EOT);
             attempts++;
             }
         while ((ModemRead(WaitTime) != ACK) && (attempts != RETRYMAX) && (timeout == FALSE));
          if (attempts == RETRYMAX)
             emits("\nNo Acknowledgment Of End Of File\n");
         };
     return TRUE;
 }
 
 /*************************************************
 *  function to output ascii chars to window
 *************************************************/
 emit(c)
 char c;
 {
 static short x = 0;
 static short y = 0;
 short xmax,ymax,cx,cy;
 
 xmax = mywindow->Width;
 ymax = mywindow->Height;
 
 /* cursor */
 if (c != 0)
    {
    if (x > (xmax-3))
       {
       cx = 7;
       cy = y + 8;
       }
    else
      {
      cx = x+8;
      cy = y;
      }
  
    if (cy > (ymax-2))
       {
       cx = 7;
       cy -= 8;
       }
 
    SetDrMd(mywindow->RPort,COMPLEMENT);
    SetAPen(mywindow->RPort,3);
    RectFill(mywindow->RPort,cx-7,cy-6,cx,cy+1);
    SetAPen(mywindow->RPort,1);
    SetDrMd(mywindow->RPort,JAM2);
 
    if (x > (xmax-3))
       {
       x = 0;
       y += 8;
       }
 
    if (y > (ymax-2))
       {
       x = 0;
       y -= 8;
       }
 
    Move(mywindow->RPort,x,y);
 
    switch( c )
          {
          case '\t':
             x += 40;
             break;
          case '\n':
             break;
          case CR:  /* carriage return */
             x = 0;
             y += 8;
             break;
          case 8:   /* backspace */
             x -= 8;
             if (x < 1)
                x = 0;
             break;
          case '\v':   /* vertical tab */
             y -=8;
             if ( y<14 )
                y = 6;
             break;
          case 12:     /* page */
             x = 0;
             y = 6;
             SetAPen(mywindow->RPort,0);
             RectFill(mywindow->RPort,0,0,xmax,ymax);
             SetAPen(mywindow->RPort,1);
             break;
          case BELL:     /* bell */
             ClipBlit(mywindow->RPort,0,0,mywindow->RPort,0,0,xmax,ymax,0x50);
             ClipBlit(mywindow->RPort,0,0,mywindow->RPort,0,0,xmax,ymax,0x50);
             break;
          default:
             Text(mywindow->RPort,&c,1);
             x += 8;
          } /* end of switch */
 /* cursor */
    if (x > (xmax-3))
       {
       cx = 7;
       cy = y + 8;
       }
    else
      { 
      cx = x+8;
      cy = y;
      }
 
    if (cy > (ymax-2))
       {
       cx = 7;
       cy -= 8;
       ScrollRaster(mywindow->RPort,0,8,0,0,xmax,ymax);
       }
 
    SetAPen(mywindow->RPort,3);
    RectFill(mywindow->RPort,cx-7,cy-6,cx,cy+1);
    SetAPen(mywindow->RPort,1);
    }
 }    /* end if (c !=0) */
 
 /*************************************************
 *  function to take raw key data and convert it 
 *  into ascii chars
 **************************************************/
 toasc(code)
 USHORT code;
 {
 static int ctrl = FALSE;
 static int shift = FALSE;
 static int capsl = FALSE;
 char c;
 static char keys[75] = {   /* 'return' and 'enter' generate chr$(13) */
 '`','1','2','3','4','5','6','7','8','9','0','-'     /* 0x0b */,
 '=','\\',0,'0','q','w','e','r','t','y','u','i','o',  /* 0x18 */
 'p','[',']',0,'1','2','3','a','s','d','f','g','h',  /* 0x25 */
 'j','k','l',';','\'',0,0,'4','5','6', 0,'z','x','c','v',  /* 0x34 */
 'b','n','m',44,'.','/',0,'.' ,'7','8','9',' ',8,         /* 0x41 */
 '\t',13,13,27,127,0,0,0,'-' } ;
      /*         del              */
              switch ( code ) /* I didn't know about the Qualifier field when I wrote this */
                     {
                     case 98:
                        capsl = TRUE;
                        c = 0;
                        break;
                     case 226:
                        capsl = FALSE;
                        c = 0;
                        break;
                     case 99:
                        ctrl = TRUE;
                        c = 0;
                        break;
                     case 227:
                        ctrl = FALSE;
                        c = 0;
                        break;
                     case 96:
                     case 97:
                        shift = TRUE;
                        c = 0;
                        break;
                     case 224:
                     case 225:
                        shift = FALSE;
                        c = 0;
                        break;
                    default:
                        if (code < 75)
                           c = keys[code];
                        else
                           c = 0;
                        }
          
 /* add modifiers to the keys */
 
 if (c != 0)
    {
 
    if (ctrl && (c <= 'z') && (c >= 'a'))
       c -= 96;
    else if (shift)
            {
       if ((c <= 'z') && (c >= 'a'))
          c -= 32;
       else
          switch( c )
                {
           case '[':
              c = '{';
              break;
           case ']':
              c = '}';
              break;
           case '\\':
              c = '|';
              break;
           case '\'':
              c = '"';
              break;
           case ';':
              c = ':';
              break;
           case '/':
              c = '?';
              break;
           case '.':
              c = '>';
              break;
           case ',':
              c = '<';
              break;
           case '`':
              c = '~';
              break;
           case '=':
              c = '+';
              break;
           case '-':
              c = '_';
              break;
           case '1':
              c = '!';
              break;
           case '2':
              c = '@';
              break;
           case '3':
              c = '#';
              break;
           case '4':
              c = '$';
              break;
           case '5':
              c = '%';
              break;
           case '6':
              c = '^';
              break;
           case '7':
              c = '&';
              break;
           case '8':
              c = '*';
              break;
           case '9':
              c = '(';
              break;
           case '0':
              c = ')';
              break;
           default:
           } /* end switch */
       } /* end shift */
    else if (capsl && (c <= 'z') && (c >= 'a'))
            c -= 32;
    } /* end modifiers */
    return(c);
 } /* end of routine */
 /* end of file */
                                              