/*
 * In this file lines end with CR/LF, as our psion C-compiler lives in the 
 * DOS-WORLD
 * Copyright GNU Public License
 */

#include <plib.h>                                 /* NFS Client V3.10 N.N */
#include <p_serial.h>
#include <wlib.h>
#include <hwif.h>
#include "nfsc.h"

#define TXT_TYPE  H_DTEXT_ALIGN_CENTRE

/*======================================================== NFSC set ===*/
#define NR_OF_DEVICES 5  /* do not change, configure over menu */

#ifdef HAVE_NFSSUPPORT
static char *dev_name[]={"LOC::M:", "LOC::A:", "LOC::B:", "LOC::C:", "ROM::"};
#endif
static char welcome[]= "Welcome to Nfsc 4.3d";

struct settings set = {
    {P_BAUD_9600,P_BAUD_9600,P_DATA_8,0,P_IGN_CTS,0x11,0x13,0,0},
    {0,0,P_SRDTR_ON},				/* modem sense, DTR assert */
    {1,1,1,1,1},                          	/* all devices active */
    FLAGS_SHOW_STATS | FLAGS_SMALL_STATWIN | FLAGS_NFSD_NEW | FLAGS_JUMPSCROLL,
    WS_FONT_BASE+4,				/* Roman 8 Point, s3 see bel. */
    XY_YMODEM1K
};


TEXT *EnvironmentName="NFSC_v32";		/* Do not use a new name */

/*========================================================== menu's used ===*/
static H_MENU_DATA mdata[]= {
#ifdef HAVE_NFSSUPPORT
    "Nfsc", 4,
#else
    "Nfsc", 2,
#endif
    "Serial Line", 5,
#ifdef HAVE_TERMEMU
    "Emulator",6,
#endif
#ifdef HAVE_XYMODEM
    "XYmodem",3,
#endif
    NULL
};

static TEXT * cmds[]= {
    /* nfsc */
        "aAbout...",
#ifdef HAVE_NFSSUPPORT
        "dDevices...",
	"nNfsd support...",
#endif
        "xExit",
    /* Serial Line */
        "pParameters...",
        "eExpert Handshake...",
        "lStatus line...",
        "hHangup",
	"bSend a break",
#ifdef HAVE_TERMEMU
    /* Emulator */
    	"rReset",
	"zZoom: bigger",
	"ZZoom: smaller",
	"fFont...",
	"FFn Keys...",
	"sSettings...",
#endif
#ifdef HAVE_XYMODEM
    /* XYmodem */
	"CConfigure...",
	"SSend file...",
	"RReceive file...",
#endif
        NULL
};

TEXT **        _cmds=cmds;
H_MENU_DATA *  _mdata=mdata;


/*===================================================== global variables ===*/
ULONG  stats_read, stats_write;	/* Read / write statistics */

P_RECT hisrect;			/* Rectangle for nfscmd history scrolling */
INT    stat_y,modem_x,io_x;	/* Y pos for stat printing (font descent),etc*/
UBYTE  charwidth,charheight,	/* Character data */
       ascent, rows, cols;
WORD   screenx,screeny,		/* Character width */
       t_w, t_h;		/* terminal width & height in pixels */
BYTE   clkwin;
BYTE   debug = 0;
BYTE   isnfsd = 0;		/* used for sending a resize event *gulp* */

extern UWORD _UseFullScreen;    /* use full s3a or only s3 screen */
char   buf[256], buf2[256];	/* serial read buffers, not on stack  */
char   rfilename[256];		/* last filename read */
VOID   *rfile = 0;		/* last read filehandle */
LONG   roff = 0;		/* last read offset */
INT    hgc;			/* handle to graphics context */
UINT   main_win;		/* main window ID */
VOID   *serial = 0;		/* handle serial port */
LOCAL_D VOID *timH;		/* handle timer */
LOCAL_D ULONG timint = 20;	/* poll interval in 100msec units */
WORD   zero=0,one=1;		/* constants to interact with serial port */
TEXT   mfb[40];			/* message formatting buffer; */
UBYTE  pctrl = 0xff;		/* store last pctrl */
UBYTE  protocol;		/* nfsd protocol none=0,old=1,new=2
                                 * new = 0x80 as escape character for nfs cmd,
				 * old = no terminal capability
				 * none = only terminal
				 */
UBYTE  sch;			/* Serial character */
WORD   ttystat;			/* We need it global */
UBYTE  is_rtscts;


#ifdef HAVE_TERMEMU

struct fkey *fk;
static char logfile[129]   = "\015\\opd\\nfsc.log";
static char fnkeyname[129] = "\017\\opd\\fnkeys.nfs";
char   cur_appl;		/* cursor keys in application mode */
BYTE   fnkey = 0;
void  *logfd = 0;

#include "cnv.h"		/* charset conversion */

#endif

static char c_handshaking[] = "Handshaking";
static char c_ignore[]      = "Ignore";
static char c_load[]        = "Load";
static char c_save[]        = "Save";
static char c_cfgserial[]   = "configure serial";
static char c_dtr[]         = "change DTR";
static char c_query[]       = "query modem";

/*========================================================= AddToHistory ===*/
void AddToHistory (char ch) {
  static char idle;
  P_POINT o;
  int i = hisrect.tl.x;

  if(! ( set.flags & FLAGS_SHOW_STATS) )
    return;

  if (!ch)		/* a heart beat indicator */
    {
      idle++;
      if (idle != 2)	/* Will wrap after 256 ticks. But that is o.k. */
        return;
      /* seen two heatbeats in a row. Print a '.' as idle indicator */
      ch = '.';		
    }
  else 
    idle = 0;

  gPrintText(hisrect.br.x-charwidth, stat_y, &ch, 1);
  o.y = 0;
  o.x = -charwidth;			
  hisrect.tl.x += charwidth;
  wScrollRect(main_win, &hisrect, &o);	/* scroll one character leftward */
  hisrect.tl.x = hisrect.br.x - charwidth;
  gClrRect(&hisrect, G_TRMODE_CLR);	/* clear space for next character */
  hisrect.tl.x = i;
}

void Check (INT val,TEXT *msg)
{
  if (!val) return;
  p_atos(&mfb[0],"Failed to %s",msg);
  if (!p_notifyerr(val,&mfb[0],"Abort",c_ignore,0))
    p_exit(0);
}

void UpdateModem()
{
  if(serial == 0 || !(set.flags & FLAGS_SHOW_STATS) )
    return;

  Check(p_iow(serial, P_FCTRL, set.pctrl), c_query);
  if (set.pctrl[0] == pctrl)
    return;
  
  pctrl = set.pctrl[0];
  
  p_atos(mfb, "%s %s %s", 
         (pctrl & P_SRCTRL_CTS) ? "CTS" : "cts",
         (pctrl & P_SRCTRL_DSR) ? "DSR" : "dsr",
         (pctrl & P_SRCTRL_DCD) ? "DCD" : "dcd");
  set.pctrl[1] = 0x00;	 /* in case we did set it here */
  gPrintText(modem_x, stat_y, mfb, p_slen(mfb));
}


/*===============  Check, if write would block, i.e. flow control is on === */
void
serial_write(char *p, int n)
{
  int ret;

  if(!(set.tty.hand & P_IGN_CTS))
    {
      Check(p_iow(serial, P_FCTRL, set.pctrl), c_query);
      while(!(set.pctrl[0] & P_SRCTRL_CTS))
	{
	  H_DI_TEXT  line1={buf+  0,TXT_TYPE}, 
		     line2={buf+ 50,TXT_TYPE};

	  uZTStoBCS (line1.str, "Hardware flowcontrol specified,");
	  uZTStoBCS (line2.str, "but modem line CTS is not set");
	  if (   uOpenDialog ("WARNING!")
	      || uAddDialogItem (H_DIALOG_TEXT, NULL, &line1)
	      || uAddDialogItem (H_DIALOG_TEXT, NULL, &line2)
	      || (uAddButtonList ("Exit",   'x',
				  c_ignore, 'i',
				  "Retry",  W_KEY_RETURN, NULL))
	      || (ret = uRunDialog()) <= 0)
		  continue;
	  if(ret == 'x')
	    p_exit(0);
	  if(ret == 'i')
	    {
	      Sw_Serial(OFF);
	      set.tty.hand |= P_IGN_CTS;
	      p_waitstat (&ttystat);
	      if ((ret=p_iow(serial,P_FSET,&set.tty)) != 0) p_panic (ret);
	      Sw_Serial(ON);
	      break;
	    }
	  Check(p_iow(serial, P_FCTRL, set.pctrl), c_query);
	}
    }
  p_write (serial, p, n);
  stats_write += n;
}

/*============================================================ ShowStats ===*/
void ShowStats (void) {

  if(! ( set.flags & FLAGS_SHOW_STATS) )
    return;

  p_atos (mfb, "I/O:%07Lu/%07Lu", stats_read, stats_write);
  gPrintText(io_x, stat_y, mfb, p_slen(mfb));
}

/*=============================================================== Resize ===*/
/* Compute every position & redraw everything */
void Resize (void)
{
    W_WINDATA	wd;
    WORD	havestat;
    P_RECT	pr;
    G_FONT_INFO fi;
    G_GC	gc;
    int		ret, l1, l2;

    /*----------------------------------- draw screen with status window ---*/

    if(clkwin)
      {
	if (_UseFullScreen) /* S3a */
	  {
	    if(clkwin == 2)
	      havestat = W_STATUS_WINDOW_BIG;
	    else
	      havestat = W_STATUS_WINDOW_SMALL;
	    wInquireStatusWindow (havestat, &wd.extent);
	    wd.extent.tl.x = wd.extent.tl.y = 0;
	    wd.extent.width = screenx - wd.extent.width;
	    wd.extent.height = screeny;
	  }
	else
	  wsScreenExt (&wd.extent);   /* Much easier */
      }
    else
      {
	wd.extent.tl.x = wd.extent.tl.y = 0;
	wd.extent.width = screenx;
	wd.extent.height = screeny;
      }

    wSetWindow (main_win, W_WIN_EXTENT, &wd);

    if ((ret=wCheckPoint()) != 0) p_exit (ret);

    pr.tl.x = pr.tl.y = 0;
    pr.br.x = wd.extent.width;
    pr.br.y = wd.extent.height;
    gClrRect(&pr, G_TRMODE_CLR);

    if(clkwin)
      {
	if(_UseFullScreen)
          wStatusWindow(havestat);
	else
          wsEnable();
      }
    else
      wsDisable();

    /*-------------------------------------------------------- Change GC ---*/
    gc.style = G_STY_MONO;
    gc.font = WS_FONT_BASE + set.font;
    gSetGC(hgc, G_GC_MASK_STYLE|G_GC_MASK_FONT, &gc);

    /*----------------- compute layout of the graphics items: ---*/
    gFontInfo(gc.font, G_STY_MONO, &fi);

    charwidth  = fi.max_width;
    charheight = fi.height;
    ascent     = fi.ascent;
    t_w	       = wd.extent.width;
    t_h        = screeny;

    if ( set.flags & FLAGS_SHOW_STATS )
      {
        t_h -= charheight;

	l1  = gTextWidth(gc.font, G_STY_MONO, "I/O:9999999/9999999", 19);
	l2  = gTextWidth(gc.font, G_STY_MONO, "CTS DSR DCD", 11);

	stat_y = screeny - fi.descent;
	io_x = 0;

	hisrect.tl.x = l1+1;
	hisrect.tl.y = screeny - charheight;
	hisrect.br.x = t_w - l2;
	hisrect.br.y = screeny;

	modem_x = hisrect.br.x;

	/*
	gDrawLine(0, t_y, t_w, t_y);
	*/

	ShowStats();
	pctrl = 0xff;
	UpdateModem();
      }

    rows = t_h / charheight;
    if(rows > 24 && set.flags & FLAGS_MAX_LINES_24)
      rows = 24;
    t_h = charheight * rows;
    cols = t_w / charwidth;  t_w = charwidth * cols;
    if(isnfsd)
      {
	/* Resize event ... */
        buf2[0] = 0; buf2[1] = rows; buf2[2] = cols;
	P_WRITE(serial, buf2, 3);
      }

#ifdef HAVE_TERMEMU
    Reset();
      
    LoadFnKey(fnkeyname, 0);

    p_atos (mfb, "rows: %d, cols: %d", rows, cols);
    wInfoMsgCorner(mfb, W_CORNER_BOTTOM_RIGHT);
#endif
}

/*=========================================== screen/menu initialization ===*/
void ScreenInitialize (void) {

    INT		len;
    G_GC	gc;

    /*-------------------------------------------------- initialize HWIF ---*/
    _UseFullScreen = TRUE;	/* Set this to False for testing S3 */
    hCrackCommandLine();
    uCommonInit();
    uEscape (FALSE);

    /*----------------------------------- read set from environment ---*/
    /* p_delenv (EnvironmentName);/* */
    len = p_getenviron (EnvironmentName, p_slen(EnvironmentName), buf);
    if (len==sizeof(set))
      p_bcpy (&set, buf, sizeof(set));
    else
      {
        if(!_UseFullScreen)
	  set.font = 0; /* S3 font for the first time */
      }
    clkwin   = (set.flags >> 2) & 0x3;
#ifdef HAVE_NFSSUPPORT
    protocol = (set.flags >> 4) & 0x3;
#else
    protocol = 0;
#endif

    /*--------------------------------------------- set process priority ---*/
    p_setpri (p_getpid(), E_MAX_PRIORITY);
    if (_UseFullScreen) wSetPriorityControl (FALSE);            /* S3a only */

    /*----------------------------------------------------- first resize ---*/
    main_win = uFindMainWid();
    if(_UseFullScreen)
      {
	screenx = 480; screeny = 160;
      }
    else
      {
        screenx = 240; screeny = 80;
      }

    gc.textmode = G_TRMODE_REPL; /* Default ist _SET */
    hgc = gCreateGC(main_win, G_GC_MASK_TEXTMODE, &gc);

    Resize();
}

/*======================================== Cancel / Reenable serial input ==*/
void Sw_Serial(char on)
{
  if(on)
    {
      one=1;
      p_ioa (serial, P_FREAD, &ttystat, &sch, &one);
    }
  else
    {
      p_iow (serial, P_FCANCEL);
    }
}

/*=============================================================== DoExit ===*/
void DoExit()
{
  int ret;

  set.flags &= ~0x3c;	/* Delete bits for clkwin & protocol */
  set.flags |= (clkwin << 2) | (protocol << 4);
  ret = p_setenviron(EnvironmentName, p_slen(EnvironmentName),
                     &set, sizeof(set));
  uErrorValue(ret);
  p_exit(0);
}

#ifdef HAVE_TERMEMU
/*=============================================================== iso2cp ===*/
unsigned char
iso2cp(unsigned char ch)			/* Called only with ch > 127 */
{
  if(set.flags & FLAGS_NOCHARCONV)
    return ch;
  else
    return tbl_iso2cp[ch-128];
}
#endif

/*=================================================== ExecuteMenuCommand ===*/
void ParseKey (UWORD key, UBYTE modifier) {

    INT    ret;

    /*--------------------------------------------------- system event ---*/
    if (key == 0x404)
      {
        wGetCommand((UBYTE *)buf);
	if(*buf == 'X')
	  DoExit();
	return;
      }
    if (key & 0x400)	/* Some other system  event */
      return;

    /*------------------------------------------- Function keys -----------*/
#ifdef HAVE_TERMEMU
    {	
      struct fkey **fp, *f;

      for(fp = &fk; *fp; fp = &(*fp)->next)
	if((*fp)->key == key && (*fp)->modifier == modifier)
	  break;
      f = *fp;

      if(fnkey)	/* Define or delete it */
	{
	  if(fnkey == 'd')
	    {
	      H_DI_TEXT  l1={buf+  0, TXT_TYPE}, 
			 l2={buf+ 50, TXT_TYPE},
			 l3={buf+100, TXT_TYPE},
			 l4={buf+150, TXT_TYPE};
	      H_DI_EDIT  l5={buf+152, 100}; 

	      uZTStoBCS(l1.str,"Type in the characters to be sent.");
	      uZTStoBCS(l2.str,"Use \\<nnn> for nonprintable (e.g. \\027=ESC)");
	      uZTStoBCS(l3.str,
	      		"and \\s<nn> for delay (e.g. \\s99=9.9 seconds)");
	      uZTStoBCS(l4.str, "");
	      uZTStoBCS(l5.str, f ? (char *)f + sizeof(struct fkey) : "");

	      if (   uOpenDialog ("Defining the function key")
		  || uAddDialogItem (H_DIALOG_TEXT, NULL, &l1)
		  || uAddDialogItem (H_DIALOG_TEXT, NULL, &l2)
		  || uAddDialogItem (H_DIALOG_TEXT, NULL, &l3)
		  || uAddDialogItem (H_DIALOG_TEXT, NULL, &l4)
		  || uAddDialogItem (H_DIALOG_EDIT, NULL, &l5))
		      return;
	      if(uRunDialog() > 0)
		AddFnKey(key, modifier, l5.str);
	    }

	  if(fnkey == 'r' && f)
	    {
	      *fp = f->next;
	      p_free(f);
	      wInfoMsgCorner("FnKey deleted", W_CORNER_BOTTOM_RIGHT);
	    }
	  fnkey = 0;
	  return;
	}
	
      if(f)	/* Was the key a function key ? */
	{
	  SendFnKey(f);
	  return;
	}
    }
#endif

    /*------------------------------------------- clock window ------------*/
    if (key == W_KEY_MENU && modifier == W_CTRL_MODIFIER)
      {
	if(--clkwin < 0)
          clkwin = _UseFullScreen ? 2 : 1;
	Resize();
        return;
      }

    /*------------------------------------- send key to the serial line ---*/
    if (!(key & W_SPECIAL_KEY) && (key!=W_KEY_MENU))
      {
#ifdef HAVE_TERMEMU
	if(key >= 256 && key <= 259) /* Arrow key */
	  {
            char mb[3];

	    mb[0] = 0x1b;
	    mb[1] = cur_appl ? 'O' : '[';
	    mb[2] = 'A' + (key - 256);
	    P_WRITE(serial, mb, 3); 
	  }
	else
	  {
	    key = key & 0xff;
	    if(key > 127 && !(set.flags & FLAGS_NOCHARCONV))
	      key = tbl_cp2iso[key-128];
	    P_WRITE(serial, (char *)&key, 1);		 /* BigEndian!!! */
	  }
#endif
	return; /* terminal */
      }

    /*------------------------- strip Psion modifier and get menu option ---*/
    key &= ~W_SPECIAL_KEY;
    if((key==W_KEY_MENU) && ((key=uPresentMenus()) <= 0))
      return;
    if (modifier & W_SHIFT_MODIFIER)
      key -= 0x20;

    /*---------------------------------------- switch on command request ---*/
    switch (key) {

        case 'a': { /*-------------------------------------------- About ---*/
            H_DI_TEXT  line1={buf+  0,TXT_TYPE}, 
	               line2={buf+ 50,TXT_TYPE},
		       line3={buf+100,TXT_TYPE},
		       line4={buf+150,TXT_TYPE},
		       line5={buf+200,TXT_TYPE};

            uZTStoBCS (line1.str, "N.N, Rudolf Knig,");
            uZTStoBCS (line2.str, "Michael Schrder, Jrgen Weigert");
            uZTStoBCS (line3.str, "¸ 1995, 1996");
            uZTStoBCS (line4.str, "jnweiger / rfkoenig@immd4.uni-erlangen.de");
            uZTStoBCS (line5.str, "Distributed under GNU Copyleft (Version 2)");

            if (   uOpenDialog ("About NFSC v4.3d")
                || uAddDialogItem (H_DIALOG_TEXT, NULL, &line1)
                || uAddDialogItem (H_DIALOG_TEXT, NULL, &line2)
                || uAddDialogItem (H_DIALOG_TEXT, NULL, &line3)
                || uAddDialogItem (H_DIALOG_TEXT, NULL, &line4)
                || uAddDialogItem (H_DIALOG_TEXT, NULL, &line5)
                || (_UseFullScreen && uAddButtonList ("", W_KEY_RETURN, NULL)) /* S3 doesn't fit */
                || uRunDialog() <= 0)
                return;

            return;
        }
#ifdef HAVE_NFSSUPPORT
        case 'd': { /*------------------------------------------ Devices ---*/
            UWORD dev[5];
            INT   i;

            if(uOpenDialog ("Devices"))
	      return;
            for(i=0; i<NR_OF_DEVICES; i++)
	      {
		dev[i] = set.devices[i] + 1;
		if(uAddChoiceList(dev_name[i], &dev[i], "Off", "On", NULL))
		  return;
	      }
            if(uRunDialog() <= 0)
	      return;
            for(i=0; i<NR_OF_DEVICES; i++)
	      set.devices[i] = dev[i] - 1;
            return;
        }
        case 'n': { /*---------------------------------------- Nfsd support */
            UWORD  prt = protocol+1;

            if (   uOpenDialog ("Protocol")
                || uAddChoiceList("Support:",&prt,
		   "Terminal only", "Old p3nfsd", "Terminal + New p3nfsd", NULL)
                || uRunDialog() <= 0)
                return;

	    protocol = prt-1;
            return;
        }
#endif
        case 27:    /* Psion-Esc */
        case 'x':   /*--------------------------------------------- Exit ---*/
	    DoExit();

/* Serial line */
        case 'p': { /*--------------------------------------------- Port ---*/
            UWORD  baud     = set.tty.tbaud;
            UWORD  databits = (set.tty.frame & P_DATA_FRM) + 1;
            UWORD  stopbits = (set.tty.frame & P_TWOSTOP) ? 2 : 1;
            UWORD  parity   = (set.tty.frame & P_PARITY) ? 
					set.tty.parity + 1 : 0x01 /*none*/;
            UWORD  ignore   = (set.tty.flags & P_IGNORE_PARITY) ? 1 : 2;
            UWORD  flow;

	    if(set.tty.hand & P_SEND_XOFF) /* Software flow control */
	      flow = 1;
	    else if(set.tty.hand & P_IGN_CTS)
	      flow = 3;
	    else
	      flow = 2;			/* Hw flow control */

            if (   uOpenDialog ("Serial line parameters")
                || uAddChoiceList ("Baud rate", &baud, "50","75","110","134",
				"150","300","600","1200","1800","2000","2400",
				"3600","4800","7200","9600", 
				_UseFullScreen ? "19200" : NULL, NULL)
                || uAddChoiceList ("Data bits",&databits,"5","6","7","8",NULL)
                || uAddChoiceList ("Stop bits", &stopbits, "1", "2", NULL)
                || uAddChoiceList ("Parity",&parity,"none","even","odd",NULL)
                || uAddChoiceList ("Ignore parity", &ignore,"yes","no", NULL)
                || uAddChoiceList ("Flowcontrol:",&flow,
		                "Software","Hardware","None", NULL)
                || uRunDialog() <= 0)
                return;

            set.tty.tbaud  = set.tty.rbaud = baud;
            set.tty.frame  = databits - 1;
            set.tty.frame |= (stopbits == 2) ? P_TWOSTOP : 0x00;
            set.tty.frame |= (parity > 1 /*none*/) ? P_PARITY  : 0x00;
            set.tty.parity = parity - 1;
            set.tty.flags  = (ignore == 1) ? P_IGNORE_PARITY : 0x00;
	    if(flow == 1)
	      set.tty.hand = P_SEND_XOFF | P_IGN_CTS;
	    else if(flow == 2)
	      set.tty.hand = 0;
	    else
	      set.tty.hand = P_IGN_CTS;
	    set.pctrl[1] = set.pctrl[2] == P_SRDTR_ON;

	    Sw_Serial(OFF);		/* otherwise panic with P_FSET! */
            p_waitstat (&ttystat);

            Check(p_iow(serial,P_FSET,&set.tty), c_cfgserial);
            Check(p_iow(serial,P_FCTRL,&set.pctrl), c_dtr);
	    set.pctrl[1] = 0x00;	/* or ModemUpdate will do it again */

	    Sw_Serial(ON);

            return;
        }
        case 'e': { /*------------------------------- Expert Handshaking ---*/
            UWORD  xon_xoff = set.tty.hand & P_SEND_XOFF ? 2 : 1;
            UWORD  rts_cts  = set.tty.hand & P_IGN_CTS   ? 2 : 1;
            UWORD  dsr_dtr  = set.tty.hand & P_OBEY_DSR  ? 2 : 1;
            UWORD  dcd      = set.tty.hand & P_OBEY_DCD  ? 2 : 1;
            UWORD  dtr      = set.pctrl[2] == P_SRDTR_ON ? 2 : 1;

            if (   uOpenDialog (c_handshaking)
                || uAddChoiceList ("Send Xon/Xoff",  &xon_xoff, "Off","On",NULL)
                || uAddChoiceList ("Ignore RTS/CTS", &rts_cts,  "Off","On",NULL)
                || uAddChoiceList ("Obey DSR/DTR",   &dsr_dtr,  "Off","On",NULL)
                || uAddChoiceList ("Obey DCD",       &dcd,      "Off","On",NULL)
                || uAddChoiceList ("Assert DTR",     &dtr,      "Off","On",NULL)
                || uRunDialog() <= 0)
                return;

            set.tty.hand  = 0x00;       /* default is ZERO for RTS/CTS */
            set.tty.hand |= (xon_xoff == 2) ? P_SEND_XOFF|P_OBEY_XOFF : 0;
            set.tty.hand |= (rts_cts  == 2) ? P_IGN_CTS   : 0x00;
            set.tty.hand |= (dsr_dtr  == 2) ? P_OBEY_DSR  : 0x00;
            set.tty.hand |= (dcd      == 2) ? P_OBEY_DCD  : 0x00;
	    set.pctrl[2] = (dtr      == 2) ? P_SRDTR_ON  : P_SRDTR_OFF;
	    set.pctrl[1] = set.pctrl[2];

	    Sw_Serial(OFF);		/* otherwise panic with P_FSET! */
            p_waitstat (&ttystat);

            Check(p_iow(serial,P_FSET,&set.tty), c_cfgserial);
            Check(p_iow(serial,P_FCTRL,&set.pctrl), c_dtr);
	    set.pctrl[1] = 0x00;	/* or ModemUpdate will do it again */

	    Sw_Serial(ON);
            return;
        }
	case 'l': {  /*-------------------------------------Status line -----*/
	  /* dialog to change update frequency: timint */
	  H_DI_NUMBER num;
	  UWORD	os, show = (set.flags & FLAGS_SHOW_STATS) ? 1 : 2;
	  UWORD clear = 2;

	  os = show;
	  num.value= (LONG *)&timint;
	  num.low = 5;
	  num.high = 1000;
	  if (   uOpenDialog("Update frequency")
	      || uAddChoiceList ("Status line visible ", &show,"Yes","No",NULL)
	      || uAddChoiceList ("Clear statistics",    &clear,"Yes","No",NULL)
	      || uAddDialogItem (H_DIALOG_NUMBER, "Update freq.(0.1secs)", &num)
	      || uRunDialog() <= 0)
	    return;
	  
	  if(os != show)
	    {
	      if(show == 1)
		set.flags |= FLAGS_SHOW_STATS;
	      else
		set.flags &= ~FLAGS_SHOW_STATS;
	      Resize();
	    }
	  if(clear == 1)
	    stats_read = stats_write = 0;
	  return;
	}
        case 'h':   /*--------------------------------------- Hangup --------*/
	  /* Lower DTR for half a second, then raise it again */

	  Sw_Serial(OFF);		/* otherwise panic with P_FSET! */
	  p_waitstat (&ttystat);

	  set.pctrl[1] = set.pctrl[2] = P_SRDTR_OFF;
	  Check(p_iow(serial,P_FCTRL,&set.pctrl), c_dtr);

	  p_sleep((ULONG)5);				/* Half a second */

	  set.pctrl[1] = set.pctrl[2] = P_SRDTR_ON;
	  Check(p_iow(serial,P_FCTRL,&set.pctrl), c_dtr);

	  set.pctrl[1] = 0;
	  Sw_Serial(ON);
	  AddToHistory ('h');
	  return;
	case 'b': {  /*-------------------------------------- Send a break --*/
	  /* We can send a break of 10/50sec = 200msec at most. Should be
	     at least 250msec, but it could be better than nothing */
          P_SRCHAR  tty;
	  unsigned char z = 0, i;

	  Sw_Serial(OFF);
	  p_iow (serial, P_FFLUSH);
	  p_waitstat (&ttystat);

	  tty = set.tty;
	  tty.tbaud = tty.rbaud = P_BAUD_50;
	  tty.frame = P_DATA_8 | P_PARITY;		 /* This means 200ms */
	  tty.parity = P_PAR_EVEN;
	  tty.hand = P_IGN_CTS;
	  Check(p_iow(serial, P_FSET, &tty), c_cfgserial);
	  for(i = 0; i < 6; i++)			/* little over 1 sec */
	    P_WRITE(serial, (char *)&z, 1);

	  Check(p_iow(serial,P_FSET,&set.tty), c_cfgserial);
	  set.pctrl[1] = 0x00;	/* or ModemUpdate will do it again */
	  p_iow (serial, P_FFLUSH);

	  Sw_Serial(ON);
	  AddToHistory ('b');
	  return;
	}

#ifdef HAVE_TERMEMU
	case 'r':    /*--------------------------------------- Reset term --*/
	  Resize();
	  return;

	case 'z':    /*------------------------------------- Zoom bigger ---*/
	    if(!_UseFullScreen)
	      return;
	    if(set.font == 12)
	      set.font = 4;
	    else
	      set.font++;
	    Resize();
	    return;
	case 'Z':    /*------------------------------------- Zoom smaller ---*/
	    if(!_UseFullScreen)
	      return;
	    if(set.font == 4)
	      set.font = 12;
	    else
	      set.font--;
	    Resize();
	    return;
	
	case 'f': {  /*------------------------------------ Choose font ---*/
            UWORD  font = set.font > 7 ? 2 : 1;
            UWORD  size = set.font - (font == 1 ? 3 : 7);

            if (   uOpenDialog ("Font & size")
                || uAddChoiceList ("Font", &font, "Roman", "Swiss", NULL)
                || uAddChoiceList ("Size", &size, "8", "11", "13", "16", NULL)
                || uRunDialog() <= 0)
                return;

	    font = (font == 1 ? 3 : 7) + size;
	    if ( font != set.font )
	      {
		set.font = font;
		Resize();
	      }
	    return;
	  }
	case 'F': {  /*--------------------------------------- Fn keys ---*/

	    char title[] = "Function keys";
	    if (uOpenDialog (title)
		|| (uAddButtonList ("Define", 'd', "Remove", 'r',
				    c_save,   's', c_load,   'l', NULL))
		|| (ret = uRunDialog()) <= 0)
		    return;
	      
	    if(ret == 'd' || ret == 'r')
	      {
		wInfoMsgCorner("Type the function key",W_CORNER_BOTTOM_RIGHT);
		fnkey = ret;
	      }
	    if(ret == 's' || ret == 'l')
	      {
		H_DI_TEXT  line1={buf+  0,TXT_TYPE}, 
			   line2={buf+ 50,TXT_TYPE};
		H_DI_FSEL  line3={ fnkeyname, 0 };
                
		if(ret=='s')
		  line3.flags |= H_FILE_NEW_EDITOR;
		uZTStoBCS (line1.str, "nfsc is autoloading \\opd\\fnkeys.nfs");
		uZTStoBCS (line2.str, "");

		if (   uOpenDialog (title)
		    || uAddDialogItem (H_DIALOG_TEXT, NULL, &line1)
		    || uAddDialogItem (H_DIALOG_TEXT, NULL, &line2)
		    || uAddDialogItem (H_DIALOG_FSEL, 
		                       ret=='s' ? c_load : c_save, &line3)
		    || uRunDialog() <= 0)
		    return;
		if(ret == 's')
		  SaveFnKey(fnkeyname);
		else
		  LoadFnKey(fnkeyname, 1);
	      }
	    return;
	  }
	case 's': {  /*------------------------------ Settings ---*/
            UWORD  cnv = (set.flags & FLAGS_NOCHARCONV) ? 2 : 1;
            UWORD  jsc = (set.flags & FLAGS_JUMPSCROLL) ? 1 : 2;
            UWORD  log = logfd ? 1 : 2;
	    UWORD  db  = debug ? 1 : 2;
	    UWORD  ml  = (set.flags & FLAGS_MAX_LINES_24) ? 1 : 2;

            if (   uOpenDialog ("Terminal emulator settings")
                || uAddChoiceList ("ISO8859-1 to IBM850", &cnv,"On","Off",NULL)
                || uAddChoiceList ("Enable jumpscroll",   &jsc,"On","Off",NULL)
                || uAddChoiceList ("Debugging",           &db, "On","Off",NULL)
                || uAddChoiceList ("Log terminal output", &log,"Yes","No",NULL)
                || uAddChoiceList ("Max. number of rows", &ml, "24","26", NULL)
                || uRunDialog() <= 0)
                return;

	    if(cnv == 1)
	      set.flags &= ~FLAGS_NOCHARCONV;
	    else
	      set.flags |= FLAGS_NOCHARCONV;

	    if(jsc == 1)
	      set.flags |= FLAGS_JUMPSCROLL;
	    else
	      set.flags &= ~FLAGS_JUMPSCROLL;

	    if(ml == 1)
	      set.flags |= FLAGS_MAX_LINES_24;
	    else
	      set.flags &= ~FLAGS_MAX_LINES_24;
	    if((ml == 1 && rows == 26) || (ml == 2 && rows == 24))
	      Resize();

	    debug = 2 - db;

	    log = 2 - log;

	    if((logfd && !log) || (!logfd && log)) /* Changed */
	      {
	        if(!log)
		  {
		    p_close(logfd);
		    logfd = 0;
		    wInfoMsgCorner("Logging terminated", W_CORNER_BOTTOM_RIGHT);
		  }
		else
		  {
		    extern char cant_open_the_file[];
		    H_DI_FSEL  line3={ logfile, H_FILE_NEW_EDITOR };

		    logfile[*logfile+1] = 0;

		    if (   uOpenDialog ("Logging")
			|| uAddDialogItem (H_DIALOG_FSEL, "Logfile:", &line3)
			|| uRunDialog() <= 0)
		      return;

		    if(p_open(&logfd,logfile+1,P_FSTREAM|P_FREPLACE|P_FUPDATE))
		      wInfoMsgCorner(cant_open_the_file, W_CORNER_BOTTOM_RIGHT);
		    else
		      wInfoMsgCorner("Logging started", W_CORNER_BOTTOM_RIGHT);
		  }
	      }
	    return;
	  }
#endif /* HAVE_TERMEMU */

#ifdef HAVE_XYMODEM
	case 'C':  /*------------------------------------------ XYmodem ----*/
	case 'S':
	case 'R':  /*------------------------------------------ XYmodem ----*/
	  /* XYmodem commands are only allowed in terminal-only mode */
	  if(protocol != 0)
	    {
		H_DI_TEXT  line1={buf+  0,TXT_TYPE}, 
			   line2={buf+ 50,TXT_TYPE};
		int ret;

		uZTStoBCS (line1.str, "Sorry, XYmodem commands are only");
		uZTStoBCS (line2.str, "allowed in the \"Terminal only\" mode");

		if (   uOpenDialog ("XYmodem commands:")
		    || uAddDialogItem (H_DIALOG_TEXT, NULL, &line1)
		    || uAddDialogItem (H_DIALOG_TEXT, NULL, &line2)
		    || (uAddButtonList ("",  W_KEY_RETURN, NULL))
		    || (ret = uRunDialog()) <= 0)
		    return;
		return;
	    }
	  DoXYmodem(key);
	  return;
#endif
    }
}


#ifdef HAVE_NFSSUPPORT
/*============================================================== Respond ===*/
VOID Respond(int rc) {

  if(protocol == 1)
    {
      P_WRITE (serial, (char *)&rc, 1); /* Big Endian!!! */
    }
  else
    {
      char ret[2];

      ret[0] = PREFIX;
      ret[1] = rc;
      P_WRITE (serial, ret, 2);
    }
}

/*=============================================================== DoWork ===*/
VOID DoWork (void) {

    struct  { LONG offset; UINT len; UBYTE req; } *parms;
    void    *file;
    char    *str;
    INT     rc, to_read;
    LONG    offset;
    P_INFO  p_info;
    UWORD   crc, crc2;
    WORD    len;

    /*-------------------------------------------------- read length&data ---*/

    len = sch;
    if(protocol == 2)	/* We are reading the length here for the new proto */
      {
	len = 0;
	P_READ (serial, &len, 1);	/* Big Endian!! */
      }
    P_READ (serial, buf, len);

    /*------------------ last (few) bytes contain request type and parms ---*/
    parms = (void *) (buf + len - sizeof(*parms));


    /*---------------- string is PASCAL string with C string termination ---*/
    str = buf+1;

    /*---------------------------------- piggyback statd code onto gattr ---*/
    if (parms->req == 0x0C) {
        AddToHistory ('f');
	Respond(0);
    }

    /*------------------------------------ last byte in frame is request ---*/
    switch (parms->req) {

    case 0x01: /*------------------------------------------------- creat ---*/
        rc = p_open (&file, str, P_FSTREAM|P_FREPLACE|P_FUPDATE);
        AddToHistory ((rc == 0) ? 'c' : 'C');
	if(!rc)
	  p_close (file);
	Respond(rc);
        return;

    case 0x02: /*------------------------------------------------- gattr ---*/
        rc = p_finfo (str, &p_info);
        AddToHistory ((rc == 0) ? 'g' : 'G');
        Respond(rc);
        if (rc) return;

        if (p_info.status & P_FADIR) {
    case 0x0C: /* statd */
            /* damned filesystem without linkcount :( */
            p_info.version = 0;
            p_scat (str, "\\");
            p_open (&file, str, P_FDIR);
            while(p_iow (file, P_FREAD, buf, (P_INFO*)buf2) != E_FILE_EOF)
              if(((P_INFO *)buf2)->status & P_FADIR)
	        p_info.version++;
            p_close (file);
        }
        P_WRITE (serial, (char *)&p_info, 12);
        return;

    case 0x03: /*--------------------------------------------------- mkd ---*/
        AddToHistory ('m');
        Respond(p_mkdir(str));
        return;

    case 0x04: /*-------------------------------------------------- read ---*/
        /* Only close the read fd after the last file chunk was read */
        offset = parms->offset;
        len = parms->len;
	rc = 0;

	if(*rfilename == 0 || p_scmp(rfilename, str))
	  {
	    if(*rfilename)
	      {
	        p_close(rfile);
		*rfilename = 0;
	      }
	    rc = p_open (&rfile,str,P_FOPEN|P_FSTREAM|P_FRANDOM|P_FSHARE);
	    if(!rc)
	      {
		p_scpy(rfilename, str);
		roff = 0;
	      }
	  }

        if(rc == 0) rc = p_finfo (str, &p_info);
        if(offset > p_info.size) offset = p_info.size;
        if(offset + len > p_info.size) len = p_info.size - offset;

        if(rc == 0 && roff != offset)
	  rc = p_seek (rfile, P_FABS, &offset);
	AddToHistory ((rc == 0) ? 'r' : 'R');
	Respond(rc);
        if(rc)
	  return;

	roff = offset;

        P_WRITE (serial, (char *)&p_info, 12);
	crc = 0;
        while(len > 0) {
            to_read = (len > sizeof(buf)) ? sizeof(buf) : len;
            to_read = p_read (rfile, buf, to_read);
	    len  -= to_read;
	    roff += to_read;

	    if(protocol > 1)
	      p_crc(&crc, (UBYTE *)buf, (UINT)to_read);
            P_WRITE (serial, buf, to_read);
        }
	if(protocol > 1)
	  P_WRITE (serial, (char *)&crc, 2);

	if(roff == p_info.size)
	  {
	    p_close (rfile);
	    *rfilename = 0;
	  }
        return;

    case 0x05: /*-------------------------------------------------- rdir ---*/
        AddToHistory ('l');
        Respond(0);
        p_open (&file, str, P_FDIR);
        while (p_iow (file, P_FREAD, buf, NULL) != E_FILE_EOF)
            P_WRITE (serial, buf, p_slen(buf)+1);
        p_close (file);
        P_WRITE(serial, (char *)&zero, 1);
        return;

    case 0x06: /*-------------------------------------------------- remv ---*/
        AddToHistory ('d');
        Respond(p_delete(str));
        return;

    case 0x07: /*-------------------------------------------------- renm ---*/
        AddToHistory ('n');
        len = ((unsigned char *)(&parms->len))[1];
        P_READ (serial, buf2, len);
        buf2[len]=0;
        rc = p_rename (str, buf2+1);
        Respond(rc);
        return;

    case 0x08: /*--------------------------------------------------- rmd ---*/
        AddToHistory ('z');
        Respond(p_delete(str));
        return;

    case 0x09: /*------------------------------------------------- sattr ---*/
        AddToHistory ('s');
        Respond(p_sfstat (str, parms->len,/*attr*/ 0x0F2F));
        return;

    case 0x0A: /*------------------------------------------------- write ---*/
        offset = parms->offset;
        len    = parms->len;

        rc = p_open (&file, str, P_FOPEN | P_FSTREAM | P_FUPDATE | P_FRANDOM);
        if(rc == 0 && offset > 0)
	  rc = p_seek (file, P_FABS, &offset);

	crc = 0;
        while (len) {
            to_read = (len > sizeof(buf)) ? sizeof(buf) : len;
            P_READ(serial, buf, to_read);
	    if(protocol > 1)
	      p_crc(&crc, (UBYTE *)buf, (UINT)to_read);
	    if(rc == 0)
              rc = p_write (file, buf, to_read);
            len -= to_read;
        }
        p_close (file);

	if(protocol > 1)
	  {
	    P_READ (serial, &crc2, 2);
	    if(crc != crc2)
	      {
		AddToHistory ('?');
		if(rc == 0)
		  rc = 1;
	      }
	  }

        Respond(rc);
        AddToHistory ((rc == 0) ? 'w' : 'W');
        return;

    case 0x0B: /*-------------------------------------------------- getd ---*/
        AddToHistory ('>');
        Respond(0);
        for(len=0; len<NR_OF_DEVICES; len++)
	  {
	    if(set.devices[len] && p_dinfo(dev_name[len], (P_DINFO *)buf) == 0)
	      {
		P_WRITE (serial, dev_name[len], p_slen(dev_name[len])+1);
		P_WRITE (serial, buf, 14);
	      }
	  }
        P_WRITE (serial, (char *)&zero, 1);
        return;

#ifdef HAVE_TERMEMU
    case 0x0D: /*------------------------------------------------- ttydata -*/
	if(protocol == 2)
	  {
	    AddToHistory ('+');
	    TtyEmu((UBYTE *)str, *(UBYTE *)buf);     /* No return this time */
	    if(isnfsd == 0)
	      {
	        isnfsd = 1;
		buf2[0] = 0;                         /* Resize event ... */
		buf2[1] = rows;
		buf2[2] = cols;
		P_WRITE(serial, buf2, 3);
	      }
	  }
	else
	  {
	    AddToHistory ('X');
	    Respond(1);
	  }
	return;
#endif

    default:   /*----------------------------------------------- default ---*/
        AddToHistory ('X');
        Respond(1);
        return;
    }
}
#endif /* HAVE_NFSSUPPORT */

#define QueueTimer(when) p_ioa4(timH,P_FRELATIVE,&timstat,&when)

/*================================================================= main ===*/
void main (void) {

    WORD       keystat, timstat;
    WMSG_KEY   key;
    char      *str;

    /*------------------------------------------- program initialization ---*/
    stats_read = stats_write = 0;
    ScreenInitialize ();
    for(str = welcome; *str; str++)
      AddToHistory(*str);

    /*------------------------------------------------- open serial port ---*/
    Check(p_open(&serial,"TTY:A",-1), "open TTY:A");
    Check(p_iow(serial,P_FSET,&set.tty), "init serial line");
    one=1; p_ioa (serial, P_FREAD, &ttystat, &sch, &one);

    if (set.flags & FLAGS_SHOW_STATS)
      UpdateModem();

    Check(p_open(&timH,"TIM:",-1), "open timer");
    QueueTimer(timint);
    uGetKeyA (&keystat, &key);

    /*-------------------------------------------------------- main loop ---*/
    for (;;) {

        /*--------------------------------- wait for something to happen ---*/
        p_iowait();

        /*------------------------------------------- was a key pressed? ---*/
        if (keystat != E_FILE_PENDING) {
            ParseKey (key.keycode, key.modifiers);
            uGetKeyA (&keystat, &key);
            continue;
        }


        /*------------------------------ did the serial port talk to us? ---*/
        if (ttystat != E_FILE_PENDING) {
	    stats_read++;
#ifdef HAVE_NFSSUPPORT
	    if(protocol == 1 || (protocol == 2 && sch == PREFIX))
	      DoWork ();
	    else
#endif
	      {
#ifdef HAVE_TERMEMU
		/* 
		   Problem: bytewise reading and screen update is too slow.
		   Let's try to read more if there is available. The problem
		   is that there may be a protocol 2 command in the data.
		   Therefore this is restricted to the "terminal only" mode.
		 */
		if(protocol == 0)
		  {
		    *buf = sch;
		    p_iow(serial, P_FTEST, &one);
		    if(one > 0) /* hmmm */
		      {
			if(one > 255) one = 255;
			p_iow (serial, P_FREAD, buf+1, &one);
		      }
		    one++;   /* the first byte is already read before */
		    TtyEmu((unsigned char *)buf, one);
		  }
		else
		  TtyEmu(&sch, 1);
#endif
	      }

            ShowStats ();
            one=1; p_ioa (serial, P_FREAD, &ttystat, &sch, &one);
            continue;
        }

        /*------------------------------------------ time for a timeout? ---*/
	if (timstat != E_FILE_PENDING)
	  {
	    UpdateModem();
            AddToHistory (0);
	    wFlush();
	    QueueTimer(timint);
	  }
    }
}
