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