/* 3270 protocol handler for the XANT program

   See IBM 3270 Information Display System Data Stream Programmer's
   Reference (GA23-0059).                                           */

#include <memory.h>
#include "s3270.h"
#include "s3270loc.h"
#define KEYS_DECLARE
#define KEYS_TABLE
#include "keys.h"


/* 3270 commands, WCCs, orders, attributes */
 
/* Command codes */
#define	CMD_W		0x01	/* Write */
#define	CMD_EW		0x05	/* Erase/Write */
#define	CMD_EW2		0x03	/* Erase/Write from 3270cn device */
#define	CMD_EWA		0x0D	/* Erase/Write Alternate */
#define	CMD_RB		0x02	/* Read Buffer */
#define	CMD_RM		0x06	/* Read Modified */
#define	CMD_RMA		0x0E	/* Read Modified All */
#define	CMD_EAU		0x0F	/* Erase All Unprotected */
#define	CMD_WSF		0x11	/* Write Structured Field */
 
/* Command codes in output 3270DS */
#define	DCMD_W		0xF1	/* Write */
#define	DCMD_EW		0xF5	/* Erase/Write */
#define	DCMD_EWA	0x7E	/* Erase/Write Alternate */
#define	DCMD_EAU	0x6F	/* Erase All Unprotected */
#define	DCMD_BSC	0xF7	/* BSC Copy */

/* Write Control Character (WCC) bit definitions */
#define	WCC_RESET	0x40	/* Reset */
#define WCC_RESERVED	0x30	/* Reserved */
#define	WCC_START	0x08	/* Start printer */
#define	WCC_ALARM	0x04	/* Sound alarm */
#define	WCC_RESTORE	0x02	/* Keyboard restore */
#define	WCC_RMDT	0x01	/* Reset modified data tags */
 
/* Order codes */
#define	ORD_SF		0x1D	/* Start Field */
#define	ORD_SFE		0x29	/* Start Field Extended */
#define	ORD_SBA		0x11	/* Set Buffer Address */
#define	ORD_SA		0x28	/* Set Attribute */
#define	ORD_MF		0x2C	/* Modify Field */
#define	ORD_IC		0x13	/* Insert Cursor */
#define	ORD_PT		0x05	/* Program Tab */
#define	ORD_RA		0x3C	/* Repeat to Address */
#define	ORD_EUA		0x12	/* Erase Unprotected to Address */
#define	ORD_GE		0x08	/* Graphic Escape */
#define ORD_XANT	0x2B	/* XANT order */

/* Function definitions for XANT order */
#define XF_MOUSE	0x01	/* Set mouse mode */
#define XF_IMAGE	0x02	/* Process image data */

/* Attribute type codes */
#define	TY_ALL		0x00	/* All character attributes */
#define TY_FA		0xC0	/* 3270 Field Attribute */
#define TY_FVALIDATE	0xC1	/* Field validation */
#define TY_FOUTLINE	0xC2	/* Field outlining */
#define TY_HILIGHT	0x41	/* Extended hilighting */
#define TY_FORE		0x42	/* Foreground color */
#define TY_CHARSET	0x43	/* Character set */
#define TY_BACK		0x45	/* Background color */
#define TY_TRANSP	0x46 	/* Background transparency */

/* Outbound structured field IDs */
#define SF_RESET	0x00	/* Reset Partition */
#define SF_READ		0x01	/* Read Partition */
#define SF_ERASE	0x03	/* Erase/Reset */
#define SF_LOADPS	0x06	/* Load Programmed Symbols */
#define SF_SETREPLY	0x09	/* Set Reply Mode */
#define SF_SETORIG	0x0B	/* Set Window Origin */
#define SF_CREATE	0x0C	/* Create Partition */
#define SF_DESTORY	0x0D	/* Destroy Partition */
#define SF_ACTIVATE	0x0E	/* Activate Partition */
#define SF_TWO		0x0F	/* Start of two-byte ID field */
#define SF_TWO2		0x10	/* Another two-byte ID field starter */
#define SF_O3270DS	0x40	/* Outbound 3270DS */
#define SF_SCS		0x41	/* SCS Data */
#define SF_SELECT	0x4A	/* Select Format Group */
#define SF_PRABS	0x4B	/* Present Absolute Format */
#define SF_PRREL	0x4C	/* Present Relative Format */

/* Second byte of outbound structured field IDs starting with 0x0F */
#define SF2_SETMSR	0x01	/* Set MSR Control */
#define SF2_DEST	0x02	/* Destination/Origin */
#define SF2_SELCOLOR	0x04	/* Select Color Table */
#define SF2_LDCOLOR	0x05	/* Load Color Table */
#define SF2_LDLINE	0x07	/* Load Line Type */
#define SF2_MODPART	0x0A	/* Modify Partition */
#define SF2_OBJDATA	0x0F	/* Object Data */
#define SF2_OBJPICT	0x10	/* Object Picture */
#define SF2_OBJCONTROL	0x11	/* Object Control */
#define SF2_OEM		0x1F	/* OEM Data */
#define SF2_CHAIN	0x21	/* Data Chain */
#define SF2_TEXT	0x71	/* Outbound Text Header */
#define SF2_IPDS	0x83	/* Select IPDS */
#define SF2_SETPRINT	0x84	/* Set Printer Characteristics */
#define SF2_BEGIN	0x85	/* Begin/End of File */
#define SF2_TYPE1	0xC1	/* Outbound Type 1 Text */

/* Second byte of outbound structured field IDs starting with 0x10 */
#define SF3_RECOVER	0x30	/* Request Recovery Data */
#define SF3_CHECKPT	0x32	/* Set Checkpoint Interval */
#define SF3_RESTART	0x33	/* Restart */
#define SF3_SAVE	0x34	/* Save/Restore Format */

/* Read Partition types */
#define RP_QUERY	0x02	/* Query */
#define RP_QUERYLIST	0x03	/* Query List */
#define RP_RMA		0x6E	/* Read Modified All */
#define RP_RB		0xF2	/* Read Buffer */
#define RP_RM		0xF6	/* Read Modified */

/* Reply modes */
#define RM_FIELD	0x00	/* Field mode */
#define RM_EXTFIELD	0x01	/* Extended field mode */
#define RM_CHAR		0x02	/* Character mode */

/* Bit definitions for Field Attributes */
#define	ATTR_GRAPHIC	0xC0	/* Make this a graphic character */
#define	ATTR_PROT	0x20	/* Field is protected */
#define	ATTR_NUMERIC	0x10	/* Field is numeric */
#define	ATTR_DISPLAY	0x0C	/* Display attributes */
#define	ATTR_NORM	0x00	/* Display/not selector-pen-detectable */
#define	ATTR_SEL	0x04	/* Display/selector-pen-detectable */
#define	ATTR_HIGH	0x08	/* Intensified/selector-pen-detectable */
#define	ATTR_INVIS	0x0C	/* Nondisplay, nondetectable */
#define	ATTR_MDT	0x01	/* Modified Data Tag */
					
/* Selected AID values */
#define AID_NONE	0x60	/* Code for no AID generated */
#define AID_RP		0x61	/* Read partition */
#define AID_SYSREQ	0xF0	/* SYS REQ or Test REQ */

/* 3277-GA definitions */
#define BGS_SF		0xB0	/* Begin graphics stream */
#define BGS_GE		0xF0	/* Begin graphics stream */
#define EGS		0xFC	/* End graphics stream */
#define ECH		0xFD	/* Echo */
#define REFR		0xFE	/* Refresh stream */

/* Default screen size */
#define DEFCOLS 80
#define DEFROWS 24

/* Some macro definitions */
#define BOOP { keyfail = True; x_ding(False); return; }
#ifndef NULL
#define NULL 0
#endif


/* Input states */
static enum states
{
  st_init, st_wcc, st_ord, st_fa, st_buffaddr, st_buffaddr2, st_ge,
  st_ra, st_ge2, st_xant, st_xf, st_mouse, st_image, st_sfe,
  st_av, st_val, st_sa, st_sa_val, st_mf, st_ga_ord, st_ga_sf,
  st_ga_buffaddr, st_ga_buffaddr2, st_ga_ge, st_ga_ra, st_ga_ge2,
  st_ga_sf2, st_wsf, st_wsflen, st_wsfid, st_wsftwo, st_wsfpid, st_wsfrp,
  st_wsferase, st_wsfsrm, st_wsfsrmatt, st_3270ds, st_wsfgraph,
  st_wsfgraph2, st_wsfgdata, st_wsfskip, st_eor
};


/* Global variables */
static unsigned char *screen;	/* Screen image */
static char *ca;		/* Character attributes */
static char *trbuff;		/* EBCDIC to ASCII translation buffer */
static char *atbuff;		/* Attribute and GA order translation buffer */
static int altcols, altrows;	/* Size of alternate screen */
static int cols, rows;		/* Size of current screen (default or alt) */
static int scrsize;		/* Character positions in current screen */
static Boolean alternate;	/* True if alternate size is being used */
static enum states state;	/* Input state */
static char *curkey;		/* Current keystroke */
static int keylen;		/* Length of keystroke (including func code) */
static field *curfield;		/* Current field */
static field *freef;		/* Free field list */
static int cursor;		/* Current cursor position */
static int writepos;		/* Current position for writes */
static unsigned char aid;	/* Current AID code */
static char reply_mode;		/* Current reply mode */
static char reply_attrlist[6];	/* Type codes returned by char mode reads */
static int reply_numattr;	/* Count of entries in reply_attrlist */
static char last_ca_sent;	/* For sendscr to track attribute changes */
static char cur_ca;		/* Current character attributes */
static int ga_pos;		/* Current position in 3277-GA buffer */
static int ga_count;		/* Count of characters since WCC for 3277-GA */
static int wsflen;		/* Bytes remaining in structured field */
static Boolean locked;		/* Keyboard lock state */
static Boolean unlock_pending;	/* Keyboard unlock is pending */
static Boolean clear_pending;	/* Screen clear is pending */
static Boolean keyfail;		/* Set to true when keyboard input fails */
static Boolean inserting;	/* True if in insert mode */
static Boolean extended;	/* True if using extended data streams */
static Boolean graphics;	/* True if 3179G-style graphics is enabled */
static Boolean ga_support;	/* True if 3277-GA support should be used */
static Boolean ga_sf;		/* True if using SF-style 3277-GA escapes */
static Boolean extcolor;	/* True if screen is in extended color mode */
static Boolean blank;		/* True if screen contains only blanks */
static Boolean capresent;	/* True if character attributes are present */


/* Forward routine declarations */
static void writestr(), writec(), writeatt(), writewrap(), writeit(),
  newfield(), delfield(), newpos(), dorepeat(), dopt(), dotypval(),
  sendall(), sendmodified(), sendfield(), sendaddr(), putcnvnull(),
  sendscr(), sendaid(), reset_mdt(), do_string(), erasee(), eraseu(),
  kflush(), setlock(), dumpfields();
static Boolean replace(), insert();


/* Externally defined variables */
extern unsigned char atoe[];	/* ASCII to EBCDIC translation table */
extern unsigned char atoge_a[];	/* APL ASCII to EBCDIC graphic escape */
extern unsigned char atoge_a_[]; /* APL(_) ASCII to EBCDIC graphic escape */
extern char etoa[];		/* EBCDIC to ASCII translation table */
extern char getoa[];		/* EBCDIC graphic escape to ASCII table */
extern unsigned char addrconv[];/* Binary to graphic symbol conversion table */
extern unsigned char otoe[], otoge[], etoo[], sftoo[], getoo[];	/* 3277-GA */
extern char alphanum[];		/* Alphanumeric character table */
extern Boolean debug;		/* Debugging flag */


/* Initialize */
s3270_init(ncols, nrows, ext, graph)
     int ncols, nrows;
     Boolean ext, graph;
{
  int altsize;

  /* Copy the settings so that we can use them later */
  altcols = ncols;
  altrows = nrows;
  extended = ext;
  graphics = graph;

  /* Set screen size to default */
  cols = DEFCOLS;
  rows = DEFROWS;
  scrsize = cols * rows;
  alternate = False;

  /* Allocate enough memory for the alternate screen size */
  altsize = altcols * altrows;
  if (altsize < scrsize) error("Screen size is too small");
  if (!(screen = (unsigned char *) malloc(altsize)) ||
      !(ca = (char *) malloc(altsize)) ||
      !(trbuff = (char *) malloc(altsize)) ||
      !(atbuff = (char *) malloc(altsize)))
    error("Not enough memory");
  memset(screen, 0, altsize);
  memset(ca, DEFAULT_CA, altsize);
  memset(trbuff, ' ', altsize);

  /* Initialize other global variables */
  state = st_init;
  curfield = NULL;
  freef = NULL;
  newcursor(0);
  newpos(0);
  aid = AID_NONE;
  reply_mode = RM_FIELD;
  setlock(True);
  unlock_pending = False;
  clear_pending = False;
  keyfail = False;
  x_clear(cols, rows);
  inserting = False;
  extcolor = False;
  blank = True;
  capresent = False;
  ga_sf = False;
  ga_count = 0;
  wsflen = 0;
  q_init();
}


/* Accept data from remote system */
s3270_accept(buff, len)
     register unsigned char *buff;
     register int len;
{
  register unsigned char c;
  register int j;
  static unsigned char cmd, order;
  static int buffaddr, insnull, field_len, numatt;
  static unsigned char fa, type;
  static char efa;
  
  while (len > 0)
    {
      c = *buff++;
      len--;
      ga_count++;

      /* At the end of a structured field, return to WSF processing */
      if (wsflen > 0 && --wsflen == 0) state = st_wsf;
      
      /* In the code which follows, the count in wsflen includes the
         data in "c" which has already been read.  If the buffer
	 contents are 0102030405, with c equal to 01, buff pointing
	 to 02, len = 4, and wsflen = 3 it means that the 0405 is
	 not part of the current structured field.  */

      switch (state) 
	{
	case st_init:
	  /* +---------------------+ */
	  /* | Looking for command | */
	  /* +---------------------+ */
	  cmd = c;
	  switch (c) 
	    {
	    case CMD_W:
	      cur_ca = DEFAULT_CA;
	      state = st_wcc;
	      break;
	      
	    case CMD_EW:
	    case CMD_EW2:
	    case CMD_EWA:
	      /* If no WCC follows, then this command should be a NOP, so
		 don't do any erasing until after we get the WCC.  */
	      state = st_wcc;
	      break;

	    case CMD_RB:
	      sendall();
	      state = st_eor;
	      break;
	      
	    case CMD_RM:
	      sendmodified(False);
	      state = st_eor;
	      break;
	      
	    case CMD_RMA:
	      sendmodified(True);
	      state = st_eor;
	      break;
	      
	    case CMD_EAU:
	      eraseu();
	      reset_mdt();
	      unlock_pending = True;
	      aid = AID_NONE;
	      state = st_eor;
	      break;
	      
	    case CMD_WSF:
	      if (extended) state = st_wsf;
	      else state = st_eor;
	      break;

	    default:
	      state = st_eor;
	      if (debug) 
		{
		  if (len || c != EBCSPACE)
		    printf("Unknown command (%d) received\n", c);
		  if (!len) break;
		  printf("Len = %d, Buff = ", len);
		  for (j = 0; j < len && j < 20; j++)
		    printf("%02X ", buff[j]);
		  putchar('\n');
		}
	      break;
	    }
	  break;

	case st_wcc:
	  /* +-------------+ */
	  /* | Reading WCC | */
	  /* +-------------+ */
	  if (c & WCC_ALARM) x_ding(True);
	  if (c & WCC_RESTORE) 
	    {
	      unlock_pending = True;
	      aid = AID_NONE;
	    }
	  if (c & WCC_RMDT) reset_mdt();
	  if ((c & WCC_RESET) && cmd != CMD_W) reply_mode = RM_FIELD;

	  /* If this is an erase command, delete any saved graphics segments.
	     Note that if the erase/write was part of a WSF outbound 3270DS
	     then the graphics aren't deleted.  I can't find any documentation
	     that says we should do this, but experimental evidence indicates
	     that this is the correct procedure.  */

	  if (cmd == CMD_EW || cmd == CMD_EW2 || cmd == CMD_EWA)
	    graph_delete();

	  /* Now clear the screen to default or alternate size */
	  if (cmd == CMD_EW || cmd == CMD_EW2 || cmd == DCMD_EW)
	    clear_screen(False);
	  else if (cmd == CMD_EWA || cmd == DCMD_EWA)
	    clear_screen(True);

	  /* Count how far into the datastream we are */
	  ga_count = 0;
	  state = st_ord;
	  break;

	case st_ord:
	  /* +---------------------+ */
	  /* | Checking for orders | */
	  /* +---------------------+ */
	  order = c;
	  if (c != ORD_PT) insnull = False;
	  switch (c) 
	    {
	    case ORD_SF:
	      state = st_fa;
	      break;
	      
	    case ORD_SFE:
	      if (extended) state = st_sfe;
	      else state = st_eor;
	      break;
	      
	    case ORD_SBA:
	      state = st_buffaddr;
	      break;
	      
	    case ORD_SA:
	      if (extended) state = st_sa;
	      else state = st_eor;
	      break;
	      
	    case ORD_MF:
	      if (extended) state = st_mf;
	      else state = st_eor;
	      break;
	      
	    case ORD_IC:
	      newcursor(writepos);
	      break;
	      
	    case ORD_PT:
	      dopt(&insnull);
	      break;
	      
	    case ORD_RA:
	      state = st_buffaddr;
	      break;
	      
	    case ORD_EUA:
	      state = st_buffaddr;
	      break;
	      
	    case ORD_GE:
	      state = st_ge;
	      break;
	      
	    case ORD_XANT:
	      state = st_xant;
	      break;

	    default:
	      insnull = True;

	      /* Write everything up to next order */
	      if (wsflen > 0)
		{
		  for (j = 0; j < len && j < (wsflen - 1) &&
		       ((buff[j] & 0xc0) || !buff[j]); j++);
		  writestr(&buff[-1], j + 1);
		  buff += j;
		  len -= j;
		  ga_count += j;
		  wsflen -= j;
		}
	      else 
		{
		  for (j = 0; j < len && ((buff[j] & 0xc0) || !buff[j]); j++);
		  writestr(&buff[-1], j + 1);
		  buff += j;
		  len -= j;
		  ga_count += j;
		}
	      break;
	    }
	  break;

	case st_fa:
	  /* +-------------------------+ */
	  /* | Reading field attribute | */
	  /* +-------------------------+ */
	  if (c == BGS_SF && ga_support && ga_count < 32)
	    {
	      atbuff[0] = sftoo[c];
	      ga_pos = 1;
	      ga_sf = True;
	      state = st_ga_ord;
	    }
	  else 
	    {
	      writeatt(c, DEFAULT_CA);
	      state = st_ord;
	    }
	  break;

	case st_buffaddr:
	  /* +------------------------+ */
	  /* | Reading buffer address | */
	  /* +------------------------+ */
	  buffaddr = c;
	  state = st_buffaddr2;
	  break;
	  
	case st_buffaddr2:
	  /* +---------------------------------------+ */
	  /* | Reading second byte of buffer address | */
	  /* +---------------------------------------+ */
	  if (buffaddr & 0xC0)
	    buffaddr = ((buffaddr & 0x3F) << 6) + (c & 0x3F);
	  else buffaddr = ((buffaddr & 0x3F) << 8) + c;

	  if (buffaddr >= scrsize) 
	    {
	      state = st_eor;
	      break;
	    }

	  switch (order) 
	    {
	    case ORD_SBA:
	      newpos(buffaddr);
	      state = st_ord;
	      break;

	    case ORD_EUA:
	      while (True)
		{
		  if (!curfield || writepos != curfield->pos &&
		      curfield->attrib & UNPROTECTED) 
		    writec(0, 0);
		  else newpos(nextpos(writepos));
		  if (writepos == buffaddr) break;
		}
	      newpos(buffaddr);
	      state = st_ord;
	      break;

	    case ORD_RA:
	      state = st_ra;
	      break;
	    }
	  break;

	case st_ge:
	  /* +---------------------------+ */
	  /* | Processing graphic escape | */
	  /* +---------------------------+ */
	  if (c == BGS_GE && ga_support && ga_count < 32)
	    {
	      atbuff[0] = getoo[c];
	      ga_pos = 1;
	      ga_sf = False;
	      state = st_ga_ord;
	    }
	  else 
	    {
	      writec(c, GRAPHIC);
	      state = st_ord;
	    }
	  break;

	case st_ra:
	  /* +-------------------------+ */
	  /* | Doing repeat to address | */
	  /* +-------------------------+ */
	  if (c == ORD_GE)
	    {
	      state = st_ge2;
	      break;
	    }
	  dorepeat(c, buffaddr, 0);
	  state = st_ord;
	  break;

	case st_ge2:
	  /* +-----------------------------+ */
	  /* | Graphic escape after ORD_RA | */
	  /* +-----------------------------+ */
	  dorepeat(c, buffaddr, GRAPHIC);
	  state = st_ord;
	  break;
	  
	case st_xant:
	  /* +------------+ */
	  /* | XANT order | */
	  /* +------------+ */

	  /* X'2B5C' is a XANT order.  It's not used by any real
	     device (I hope).  */

	  if (c == 0x5C) state = st_xf;
	  else state = st_eor;
	  break;
	  
	case st_xf:
	  /* +---------------------------------+ */
	  /* | Processing XANT function string | */
	  /* +---------------------------------+ */
	  switch (c)
	    {
	    case XF_MOUSE:
	      state = st_mouse;
	      break;

	    case XF_IMAGE:
	      state = st_image;
	      break;

	    default:
	      state = st_eor;
	      break;
	    }
	  break;

	case st_mouse:
	  /* +------------------------------------+ */
	  /* | Processing set mouse mode function | */
	  /* +------------------------------------+ */
	  x_mouse((Boolean) (c == 1));
	  state = st_ord;
	  break;
	  
	case st_image:
	  /* +-----------------------+ */
	  /* | Processing image data | */
	  /* +-----------------------+ */
	  j = image_write(--buff, ++len);
	  if (j >= 0)
	    {
	      buff += len - j;
	      len = j;
	      state = st_ord;
	    }
	  else len = 0;
	  /* The case of an image write in an output 3270DS isn't handled... */
	  break;

	case st_sfe:
	  /* +---------------------------------+ */
	  /* | Processing Start Field Extended | */
	  /* +---------------------------------+ */
	  numatt = c;
	  fa = 0x40;
	  efa = DEFAULT_CA;
	  if (numatt) state = st_av;
	  else 
	    {
	      writeatt(fa, efa);
	      state = st_ord;
	    }
	  break;

	case st_av:
	  /* +------------------------------------+ */
	  /* | Reading attribute type/value pairs | */
	  /* +------------------------------------+ */
	  type = c;
	  state = st_val;
	  break;

	case st_val:
	  /* +------------------------------------+ */
	  /* | Reading the value part of A/V pair | */
	  /* +------------------------------------+ */
	  dotypval(type, c, &fa, &efa);
	  if (--numatt == 0)
	    {
	      writeatt(fa, efa);
	      state = st_ord;
	    }
	  else state = st_av;
	  break;

	case st_sa:
	  /* +--------------------------+ */
	  /* | Processing Set Attribute | */
	  /* +--------------------------+ */
	  type = c;
	  state = st_sa_val;
	  break;
	  
	case st_sa_val:
	  /* +----------------------------------------+ */
	  /* | Reading attribute value after SA order | */
	  /* +----------------------------------------+ */
	  dotypval(type, c, &fa, &cur_ca);
	  state = st_ord;
	  break;
	  
	case st_mf:
	  /* +-------------------------+ */
	  /* | Processing Modify Field | */
	  /* +-------------------------+ */
	  if (!curfield || curfield->pos != writepos) 
	    {
	      state = st_eor;
	      break;
	    }
	  fa = curfield->attrib;
	  efa = ca[writepos];
	  numatt = c;
	  if (numatt) state = st_av;
	  else state = st_ord;
	  break;

	case st_ga_ord:
	  /* +---------------------------+ */
	  /* | Processing 3277-GA stream | */
	  /* +---------------------------+ */
	  switch (c) 
	    {
	    case ORD_SF:
	      state = st_ga_sf;
	      break;
	      
	    case ORD_SBA:
	      order = c;
	      state = st_ga_buffaddr;
	      break;
	      
	    case ORD_IC:
	      break;
	      
	    case ORD_RA:
	      order = c;
	      state = st_ga_buffaddr;
	      break;
	      
	    case ORD_GE:
	      state = st_ga_ge;
	      break;
	      
	    default:
	      if (ga_pos < scrsize) atbuff[ga_pos++] = etoo[c];
	    }
	  break;

	case st_ga_sf:
	  /* +------------------------------+ */
	  /* | Processing 3277-GA SF escape | */
	  /* +------------------------------+ */
	  c = sftoo[c];
	  if (ga_pos < scrsize) atbuff[ga_pos++] = c;
	  if (c == EGS || c == ECH || c == REFR)
	    {
	      ga_write(atbuff, ga_pos);
	      clear_pending = True;
	      state = st_ord;
	    }
	  else state = st_ga_ord;
	  break;
	  
	case st_ga_buffaddr:
	  /* +-------------------------------------+ */
	  /* | Reading buffer address in GA stream | */
	  /* +-------------------------------------+ */
	  buffaddr = c;
	  state = st_ga_buffaddr2;
	  break;
	  
	case st_ga_buffaddr2:
	  /* +---------------------------------------+ */
	  /* | Reading second byte of buffer address | */
	  /* +---------------------------------------+ */
	  if (buffaddr & 0xC0)
	    buffaddr = ((buffaddr & 0x3F) << 6) + (c & 0x3F);
	  else buffaddr = ((buffaddr & 0x3F) << 8) + c;

	  if (buffaddr >= scrsize) state = st_eor;
	  else switch (order) 
	    {
	    case ORD_SBA:
	      ga_pos = buffaddr;
	      state = st_ga_ord;
	      break;

	    case ORD_RA:
	      state = st_ga_ra;
	      break;
	    }
	  break;

	case st_ga_ge:
	  /* +----------------------------------------+ */
	  /* | Processing graphic escape in GA stream | */
	  /* +----------------------------------------+ */
	  c = getoo[c];
	  if (ga_pos < scrsize) atbuff[ga_pos++] = c;
	  if (c == EGS || c == ECH || c == REFR)
	    {
	      ga_write(atbuff, ga_pos);
	      clear_pending = True;
	      state = st_ord;
	    }
	  else state = st_ga_ord;
	  break;

	case st_ga_ra:
	  /* +--------------------------------------+ */
	  /* | Doing repeat to address in GA stream | */
	  /* +--------------------------------------+ */
	  if (c == ORD_GE) state = st_ga_ge2;
	  else if (c == ORD_SF) state = st_ga_sf2;
	  else 
	    {
	      while (ga_pos < buffaddr) 
		atbuff[ga_pos++] = etoo[c];
	      state = st_ga_ord;
	    }
	  break;

	case st_ga_ge2:
	  /* +------------------------------------------+ */
	  /* | Graphic escape after ORD_RA in GA stream | */
	  /* +------------------------------------------+ */
	  while (ga_pos < buffaddr) 
	    atbuff[ga_pos++] = getoo[c];
	  state = st_ga_ord;
	  break;
	  
	case st_ga_sf2:
	  /* +------------------------------+ */
	  /* | SF after ORD_RA in GA stream | */
	  /* +------------------------------+ */
	  while (ga_pos < buffaddr) 
	    atbuff[ga_pos++] = sftoo[c];
	  state = st_ga_ord;
	  break;
	  
	case st_wsf:
	  /* +----------------------+ */
	  /* | WSF command received | */
	  /* +----------------------+ */
	  field_len = c << 8;
	  state = st_wsflen;
	  break;

	case st_wsflen:
	  /* +------------------------------------+ */
	  /* | Reading length of structured field | */
	  /* +------------------------------------+ */
	  wsflen = field_len | c;

	  /* Length of zero means use whole record */
	  if (!wsflen) wsflen = 65536;

	  /* Don't count the byte that we already processed */
	  wsflen--;
	  state = st_wsfid;
	  break;
	  
	case st_wsfid:
	  /* +-------------------------+ */
	  /* | Got structured field ID | */
	  /* +-------------------------+ */
	  order = c;
	  switch (c)
	    {
	    case SF_READ:
	      state = st_wsfpid;
	      break;

	    case SF_ERASE:
	      state = st_wsferase;
	      break;

	    case SF_SETREPLY:
	      state = st_wsfpid;
	      break;

	    case SF_O3270DS:
	      state = st_wsfpid;
	      break;

	    case SF_TWO:
	      state = st_wsftwo;
	      break;
	      
	    default:
	      state = st_wsfskip;
	    }
	  break;
	  
	case st_wsftwo:
	  /* +----------------------------------------+ */
	  /* | Reading second byte of two-byte WSF ID | */
	  /* +----------------------------------------+ */
	  order = c;
	  state = st_wsfpid;
	  break;
	  
	case st_wsfpid:
	  /* +------------------------------+ */
	  /* | Reading Partition Identifier | */
	  /* +------------------------------+ */

	  /* We don't support partitions, so ignore the ID.  It might
	     be nice to add a check for legal values here... */

	  switch (order) 
	    {
	    case SF_READ:
	      state = st_wsfrp;
	      break;

	    case SF_SETREPLY:
	      state = st_wsfsrm;
	      break;

	    case SF_O3270DS:
	      state = st_3270ds;
	      break;

	    case SF2_OBJDATA:
	    case SF2_OBJPICT:
	    case SF2_OBJCONTROL:
	      state = st_wsfgraph;
	      break;
	      
	    default:
	      state = st_wsfskip;
	    }
	  break;
	  
	case st_wsfrp:
	  /* +--------------------------------+ */
	  /* | Doing Read Partition; Get Type | */
	  /* +--------------------------------+ */
	  setlock(True);
	  switch (c)
	    {
	    case RP_QUERY:
	      reply_send(altcols, altrows, graphics);
	      break;

	    case RP_QUERYLIST:
	      /* Not implemented yet...treat it like RP_QUERY */
	      reply_send(altcols, altrows, graphics);
	      break;
	      
	    case RP_RMA:
	      aid = AID_RP;
	      sendmodified(True);
	      break;

	    case RP_RB:
	      aid = AID_RP;
	      sendall();
	      break;

	    case RP_RM:
	      aid = AID_RP;
	      sendmodified(False);
	      break;
	    }
	  state = st_wsfskip;
	  break;

	case st_wsferase:
	  /* +-----------------------------------------+ */
	  /* | Processing Erase/Reset Structured Field | */
	  /* +-----------------------------------------+ */
	  graph_delete();
	  clear_screen((Boolean) ((c & 0x80) != 0));
	  reply_mode = RM_FIELD;
	  state = st_wsfskip;
	  break;

	case st_wsfsrm:
	  /* +--------------------------------------------+ */
	  /* | Processing Set Reply Mode Structured Field | */
	  /* +--------------------------------------------+ */
	  if (c == RM_FIELD || c == RM_EXTFIELD || c == RM_CHAR)
	    reply_mode = c;
	  state = st_wsfskip;

	  if (c == RM_CHAR)
	    {
	      reply_numattr = 0;
	      state = st_wsfsrmatt;
	    }
	  break;

	case st_wsfsrmatt:
	  /* +-----------------------------------------------------+ */
	  /* | Processing Set Reply Mode character attribute list  | */
	  /* +-----------------------------------------------------+ */

	  /* If this isn't a valid character attribute, ignore it */
	  if (c != TY_ALL && c != TY_HILIGHT && c != TY_FORE &&
	      c != TY_CHARSET && c != TY_BACK && c != TY_TRANSP)
	    break;
	  
	  /* If this attribute is already in the list, then skip it */
	  if (memchr(reply_attrlist, c, reply_numattr)) break;

	  /* Double check to make sure we don't overflow the array */
	  if (reply_numattr >= sizeof reply_attrlist) break;

	  /* Store new attribute in the list */
	  reply_attrlist[reply_numattr++] = c;
	  break;

	case st_3270ds:
	  /* +---------------------------------------------+ */
	  /* | Processing Outbound 3270DS Structured Field | */
	  /* +---------------------------------------------+ */
	  cmd = c;
	  switch (c) 
	    {
	    case DCMD_W:	/* Write */
	      cur_ca = DEFAULT_CA;
	      state = st_wcc;
	      break;

	    case DCMD_EW:	/* Erase/Write */
	    case DCMD_EWA:	/* Erase/Write Alternate */
	      state = st_wcc;
	      break;

	    case DCMD_EAU:	/* Erase All Unprotected */
	      eraseu();
	      reset_mdt();
	      unlock_pending = True;
	      aid = AID_NONE;
	      state = st_wsfskip;
	      break;

	    case DCMD_BSC:	/* BSC Copy */
	      /* Not supported--ignore */
	      state = st_wsfskip;
	      break;

	    default:
	      state = st_wsfskip;
	      break;
	    }
	  break;

	case st_wsfgraph:
	  /* +-----------------------------------------+ */
	  /* | Graphics information; reading flag byte | */
	  /* +-----------------------------------------+ */
	  graph_start(order, (Boolean) ((c & 0x80) != 0),
		      (Boolean) ((c & 0x40) != 0));
	  state = st_wsfgraph2;
	  break;

	case st_wsfgraph2:
	  /* +------------------------------------------+ */
	  /* | Graphics information; skip reserved byte | */
	  /* +------------------------------------------+ */
	  state = st_wsfgdata;
	  break;

	case st_wsfgdata:
	  /* +--------------------------------+ */
	  /* | Graphics information; get data | */
	  /* +--------------------------------+ */
	  buff--;
	  len++;
	  if (len <= wsflen)
	    {
	      graph_write(buff, len);
	      ga_count += len;
	      wsflen -= (len - 1);
	      len = 0;
	      if (wsflen == 1) graph_end();
	    }
	  else 
	    {
	      graph_write(buff, wsflen);
	      buff += wsflen;
	      len -= wsflen;
	      ga_count += wsflen;
	      wsflen = 1;
	      graph_end();
	    }
	  break;

	case st_wsfskip:
	  /* +-------------------------------------+ */
	  /* | Skipping to end of structured field | */
	  /* +-------------------------------------+ */
	  if (len >= (wsflen - 1))
	    {
	      len -= (wsflen - 1);
	      buff += (wsflen - 1);
	      ga_count += (wsflen - 1);
	      wsflen = 1;
	    }
	  else 
	    {
	      ga_count += len;
	      wsflen -= len;
	      len = 0;
	    }
	  break;

	case st_eor:
	  /* +------------------+ */
	  /* | Scanning for EOR | */
	  /* +------------------+ */
	  ga_count += len;
	  len = 0;
	  break;
	}
    }  
}


/* Accept end of record from remote system */
s3270_eor()
{
  state = st_init;
  wsflen = 0;
  image_end();
  if (clear_pending)
    {
      clear_screen(alternate);
      clear_pending = False;
    }
  if (unlock_pending) 
    {
      setlock(False);
      unlock_pending = False;
    }
  kflush();
}


/* Receive keyboard input.  Return true if successful.  */
Boolean s3270_key(key, nbytes)
     char *key;
     int nbytes;
{
  keyfail = False;

  /* If keyboard isn't locked, put key at end of queue and then flush queue */
  if (!locked) 
    {
      if (!q_put(key, nbytes)) 
	{
	  x_ding(False);
	  return False; 
	}
      kflush();
      return !keyfail;
    }
  
  /* Keyboard is locked, so check for functions that work when locked */
  keylen = nbytes;
  if (key[0] == k_reset || key[0] == k_unlock)
    (key_function[key[0]])();

  /* Otherwise queue the keystroke */
  else if (!q_put(key, nbytes))
    {
      x_ding(False);
      return False; 
    }
  return !keyfail;
}


/* Turn 3277-GA support off or on */
s3270_ga(val)
     Boolean val;
{
  ga_support = val;
}


/* Retrieve some values for use by application program interface */
s3270_getvals(ncols, nrows, curspos, ext, lock)
     int *ncols, *nrows, *curspos;
     Boolean *ext, *lock;
{
  *ncols = cols;
  *nrows = rows;
  *curspos = cursor;
  *ext = extended;
  *lock = locked;
}


/* Retrieve info about a field for use by API.  Return 0 if successful */
int s3270_getfield(somepos, dir, ftype, startpos, len, attrib)
     int somepos;		/* Position of some character in the field */
     char dir, ftype;		/* Direction and field type */
     int *startpos, *len;	/* Starting pos and length returned here */
     char *attrib;		/* 3270 field attribute */
{
  register field *f;

  /* Check for position outside of screen */
  if (somepos < 0 || somepos >= scrsize) return 1;

  /* If screen is not formatted, return zero attribute */
  if (!curfield)
    {
      *attrib = 0;
      return 0;
    }
  
  /* Loop through fields until we find the one for this position */
  f = curfield;
  while (True)
    {
      int pos = somepos - f->pos;
      if (pos < 0) pos += scrsize;
      if (pos < f->len) break;
      f = f->next;
    }

  /* Check for skip to next or previous field */
  if (dir != 0)
    {
      field *startf = f;
      while (True)
	{
	  f = (dir == 1 ? f->next : f->prev);
	  if (f == startf) break;
	  if (ftype == 0 || ftype == 1 && (f->attrib & UNPROTECTED) == 0 ||
	      ftype == 2 && (f->attrib & UNPROTECTED) != 0) break;
	}

      /* If we didn't find the right field type then return non-zero */
      if (ftype == 1 && (f->attrib & UNPROTECTED) != 0 ||
	  ftype == 2 && (f->attrib & UNPROTECTED) == 0) return 1;
    }
  
  /* Return info about the field */
  *startpos = nextpos(f->pos);
  *len = f->len - 1;
  *attrib = screen[f->pos];
  return 0;
}



/*** Private routines ***/


/* Clear the screen */
clear_screen(alt)
     Boolean alt;
{
  if (curfield) 
    {
      curfield->prev->next = freef;
      freef = curfield;
      curfield = NULL;
    }
  cols = alt ? altcols : DEFCOLS;
  rows = alt ? altrows : DEFROWS;
  scrsize = cols * rows;
  alternate = alt;
  memset(screen, 0, scrsize);
  memset(ca, DEFAULT_CA, scrsize);
  memset(trbuff, ' ', scrsize);
  x_clear(cols, rows);
  writepos = 0;
  newcursor(0);
  cur_ca = DEFAULT_CA;
  extcolor = False;
  blank = True;
  capresent = False;
}


/* Write a string at the current writing position */
static void writestr(buff, len)
     register unsigned char *buff;
     register int len;
{
  register int j;
  int len2;
  if (!curfield)
    {
      /* Handle screen wrap */
      len2 = writepos + len - scrsize;
      if (len2 > 0) len -= len2;

      /* Copy string to screen */
      memcpy(&screen[writepos], buff, len);
      blank = False;
      memset(&ca[writepos], cur_ca, len);
      if (cur_ca != DEFAULT_CA) capresent = True;
      for (j = writepos; j < writepos + len; j++)
	trbuff[j] = (cur_ca & CHARSET) ? getoa[screen[j]] : etoa[screen[j]];

      /* Write it out */
      writeit(writepos, len, 0, cur_ca);
      writepos += len;
      if (writepos == scrsize) writepos = 0;
      if (len2 > 0) writestr(&buff[len], len2);
    }
  else while (len > 0)
    {
      /* If writing on top of a field attribute, let writec handle it */
      if (writepos == curfield->pos)
	{
	  writec(buff[0], 0);
	  buff++;
	  len--;
	}
      else
	{
	  /* Figure out length remaining in this field with no wrapping */
	  int endpos = curfield->next->pos;
	  if (endpos < writepos) endpos = scrsize;
	  len2 = endpos - writepos;
	  if (len2 > len) len2 = len;

	  /* Copy string to screen */
	  memcpy(&screen[writepos], buff, len2);
	  blank = False;
	  memset(&ca[writepos], cur_ca, len2);
	  if (cur_ca != DEFAULT_CA) capresent = True;
	  for (j = writepos; j < writepos + len2; j++)
	    trbuff[j] = (cur_ca & CHARSET) ? getoa[screen[j]] :
	      etoa[screen[j]];

	  /* Write it out */
	  writeit(writepos, len2, curfield->attrib, ca[curfield->pos]);
	  writepos += len2;
	  if (writepos == scrsize) writepos = 0;
	  newpos(writepos);

	  buff += len2;
	  len -= len2;
	}
    }
}


/* Write a character at the current writing position */
static void writec(c, graphic)
     unsigned char c;
     char graphic;
{
  screen[writepos] = c;
  if (c && c != EBCSPACE) blank = False;
  ca[writepos] = cur_ca | graphic;
  if ((cur_ca | graphic) != DEFAULT_CA) capresent = True;
  trbuff[writepos] = graphic | (cur_ca & CHARSET) ? getoa[c] : etoa[c];

  /* Check for writing on top of a field attribute */
  if (curfield && writepos == curfield->pos)
    {
      /* If attribute changes, redraw the field */
      if (curfield->attrib != curfield->prev->attrib ||
	  ca[writepos] != ca[curfield->prev->pos])
	writewrap(nextpos(writepos), curfield->len - 1,
		  curfield->prev->attrib, ca[curfield->prev->pos]);
      delfield();
    }

  /* Redraw the character */
  if (curfield) writeit(writepos, 1, curfield->attrib, ca[curfield->pos]);
  else writeit(writepos, 1, 0, DEFAULT_CA);

  /* Increment the current writing position */
  if (++writepos == scrsize) writepos = 0;
  newpos(writepos);
}


/* Write a new field attribute at the current writing position */
static void writeatt(fa, efa)
     register unsigned char fa;
     char efa;
{
  char attrib, oldattrib, oldefa;

  screen[writepos] = fa;
  trbuff[writepos] = ' ';

  /* Save old attribute value */
  if (curfield)
    {
      oldattrib = curfield->attrib & ~NUMERIC;
      oldefa = ca[curfield->pos];
    }
  else
    {
      oldattrib = 0;
      oldefa = DEFAULT_CA;
    }

  /* Compute new attribute value */
  attrib = FORMATTED;
  if (!(fa & ATTR_PROT)) attrib |= UNPROTECTED;
  if ((fa & ATTR_DISPLAY) == ATTR_HIGH) attrib |= INTENSE;
  else if ((fa & ATTR_DISPLAY) == ATTR_INVIS) attrib |= INVISIBLE;
  if (fa & ATTR_NUMERIC) attrib |= NUMERIC;

  /* If writing on a normal character, create a new field */
  if (!curfield || writepos != curfield->pos) newfield();

  /* Store the new attribute value */
  curfield->attrib = attrib;
  ca[writepos] = efa;

  /* The NUMERIC bit doesn't affect the display, so wipe it out */
  attrib &= ~NUMERIC;

  /* Attribute is translated to a space on the screen */
  x_write(writepos, &trbuff[writepos], &attrib, 1, False);

  /* If attribute changes, redraw the whole field */
  if (((attrib != oldattrib || efa != oldefa) && !blank) ||
      (efa & HI_MASK) != (oldefa & HI_MASK))
    writewrap(nextpos(writepos), curfield->len - 1, attrib, efa);
  
  /* Increment the current writing position */
  if (++writepos == scrsize) writepos = 0;
  newpos(writepos);
}


/* Call writeit routine after taking care of screen wrap */
static void writewrap(pos, len, attrib, efa)
     int pos;
     int len;
     char attrib, efa;
{
  int len2 = pos + len - scrsize;
  if (len2 > 0) 
    {
      len -= len2;
      writeit(pos, len, attrib, efa);
      writeit(0, len2, attrib, efa);      
    }
  else writeit(pos, len, attrib, efa);
}


/* Translate attributes and call X screen-writing routine */
static void writeit(pos, len, attrib, efa)
     int pos;
     int len;
     char attrib, efa;
{
  register int j;
  char *buff = &trbuff[pos];
  
  attrib &= ~NUMERIC;
  if (attrib & INVISIBLE) buff = NULL;
  if (!capresent) 
    {
      if (extcolor) 
	{
	  if ((efa & EXT_MASK) == 0 && (attrib & INTENSE))
	    attrib = IC_DEFINTENSE;
	  else attrib = efa | IC_EXTCOLOR;
	}
      attrib |= efa & HI_MASK;
      x_write(pos, buff, &attrib, len, capresent);
    }
  else
    {
      for (j = 0; j < len; j++)
	{
	  register char attr = ca[pos + j];
	  if (extcolor)
	    {
	      if ((attr & EXT_MASK) == 0) attr |= efa & EXT_MASK;
	      if ((attr & EXT_MASK) == 0 && (attrib & INTENSE))
		attr |= IC_DEFINTENSE;
	      else attr |= IC_EXTCOLOR;
	    }
	  else attr = attrib & EXT_MASK;
	  if ((attr & HI_MASK) == 0) attr |= efa & HI_MASK;
	  atbuff[j] = attr;
	}
      x_write(pos, buff, atbuff, len, capresent);
    }
}


/* Create a new field at the current writing position */
static void newfield()
{
  register field *f;
  if (freef) 
    {
      f = freef;
      freef = freef->next;
    }
  else if (!(f = (field *) malloc(sizeof(field))))
    error("Not enough memory");
  f->pos = writepos;
  if (!curfield)
    {
      f->next = f;
      f->prev = f;
      f->len = scrsize;
    }
  else 
    {
      f->next = curfield->next;
      f->prev = curfield;
      curfield->next = f;
      f->next->prev = f;

      f->len = f->next->pos - writepos;
      if (f->len <= 0) f->len += scrsize;
      f->prev->len = writepos - f->prev->pos;
      if (f->prev->len <= 0) f->prev->len += scrsize;
    }
  curfield = f;
}


/* Delete the current field */
static void delfield()
{
  register field *f;
  f = curfield;
  if (f->next == f)
    curfield = NULL;
  else 
    {
      curfield = f->prev;
      f->prev->len += f->len;
      f->prev->next = f->next;
      f->next->prev = f->prev;
    }

  /* We never free the field storage.  This wastes a little space
     but saves time, and time is more important.  */

  f->next = freef;
  freef = f;
}


/* Change the current writing position, updating current field */
static void newpos(pos)
     register int pos;
{
  register int p;
  writepos = pos;
  if (!curfield) return;

  while (True)
    {
      p = writepos - curfield->pos;
      if (p < 0) p += scrsize;
      if (p < curfield->len) break;
      curfield = curfield->next;
    }
}


/* Return the current cursor position */
int getcursor()
{
  return cursor;
}


/* Set the cursor to a new location */
newcursor(pos)
     register int pos;
{
  if (pos >= scrsize) pos -= scrsize;
  else if (pos < 0) pos += scrsize;
  cursor = pos;
  x_cursor(cursor);
}


/* Find the field that the cursor is in */
field *cursfield() 
{
  register int pos;
  register field *f;

  if (!curfield) return (field *) NULL;
  f = curfield;
  while (True)
    {
      pos = cursor - f->pos;
      if (pos < 0) pos += scrsize;
      if (pos < f->len) break;
      f = f->next;
    }
  return f;
}


/* Process repeat to address order */
static void dorepeat(c, buffaddr, graphic)
     register unsigned char c;
     register int buffaddr;
     char graphic;
{
  do writec(c, graphic);
  while (writepos != buffaddr);
}


/* Process program tab order */
static void dopt(insnull)
     int *insnull;
{
  register int pos;
  register field *f;
  
  /* Take care of unformatted screens */
  if (!curfield) 
    {
      if (*insnull) clear_screen(alternate);
      newpos(0);
      *insnull = False;
      return;
  }

  /* If current buffer address is the location of a field attribute
     of an unprotected field, just advance one location.  */

  if (writepos == curfield->pos && curfield->attrib & UNPROTECTED
      && curfield->len > 1)
    {
      writepos = nextpos(writepos);
      *insnull = False;
      return;
    }

  /* Position to next field, nulling out current one if necessary */
  if (*insnull)
    {
      for (;writepos != curfield->pos;)
	{
	  writec(0, 0);
	  if (writepos == 0) return; /* Don't wrap past end of screen */
	}
      *insnull = False;
    }
  else 
    {
      pos = curfield->next->pos;
      if (pos <= writepos)	/* Don't wrap past end of screen */
	{
	  newpos(0);
	  return;
	}
      newpos(pos);
    }
  
  /* Position to start of next unprotected field, but don't wrap */
  pos = writepos;
  for (f = curfield;;) 
    {
      if (f->attrib & UNPROTECTED && f->len > 1)
	{
	  newpos(nextpos(f->pos));
	  return;
	}
      f = f->next;
      if (f == curfield || f->pos <= writepos) break;
    }

  /* Couldn't find an unprotected field */
  newpos(0);
}


/* Process an extended field or character attribute type/value pair */
static void dotypval(type, val, fa, efa)
     unsigned char type, val, *fa;
     char *efa;
{
  switch (type) 
    {
    case TY_ALL:
      *efa = DEFAULT_CA;
      break;
      
    case TY_FA:
      *fa = val;
      break;
      
    case TY_HILIGHT:
      switch (val) 
	{
	case 0xF1:
	  *efa = *efa & ~HI_MASK | HI_BLINK;
	  break;
	case 0xF2:
	  *efa = *efa & ~HI_MASK | HI_REVERSE;
	  break;
	case 0xF4:
	  *efa = *efa & ~HI_MASK | HI_UNDER;
	  break;
	default:
	  *efa = *efa & ~HI_MASK;
	  break;
	}
      break;
      
    case TY_FORE:
      *efa = *efa & ~EXT_MASK | val & EXT_MASK;
      if (extcolor) break;

      /* Redraw screen if this is the first time extended colors were used */
      extcolor = True;
      if (blank) break;
      if (curfield) 
	{
	  register field *f = curfield;
	  while (True)
	    {
	      writewrap(nextpos(f->pos), f->len - 1, f->attrib,
			ca[f->pos]);
	      f = f->next;
	      if (f == curfield) break;
	    }
	}
      else writeit(0, scrsize, 0, cur_ca);
      break;
      
    case TY_CHARSET:
      /* We only support one alternate character set with local ID 0xF1 */
      *efa = *efa & ~CHARSET;
      if (val == 0xF1) *efa |= CHARSET;
      break;

    default:
      break;
    }
}


/* Send the whole buffer */
static void sendall()
{
  register int pos;
  register field *f;
  unsigned char attrbuff[20];
  int attrlen;
  
  sendaddr(aid, cursor);

  last_ca_sent = DEFAULT_CA;
  if (!curfield) 
    /* If screen is unformatted, just send it */
    sendscr(0, scrsize);
  else 
    {
      /* Find the field for screen position zero */
      f = curfield;
      while (True)
	{
	  pos = -f->pos;
	  if (pos < 0) pos += scrsize;
	  if (pos < f->len) break;
	  f = f->next;
	}

      /* Send everything up to the first field attribute */
      if (f->pos) 
	{
	  sendscr(0, f->next->pos);
	  f = f->next;
	}

      /* Send the rest of the screen, inserting start field orders */
      attrbuff[0] = (reply_mode == RM_FIELD) ? ORD_SF : ORD_SFE;
      while (True)
	{
	  attrlen = 1;

	  /* If we're in field mode, just send the field attribute */
	  if (reply_mode == RM_FIELD)
	    attrbuff[attrlen++] = screen[f->pos];

	  /* Otherwise, send count followed by list of attribute/value pairs */
	  else
	    {
	      char efa;
	      attrbuff[attrlen++] = 0;	/* Number of pairs */
	      if (screen[f->pos] != 0x40)
		{
		  attrbuff[attrlen++] = TY_FA;
		  attrbuff[attrlen++] = screen[f->pos];
		  attrbuff[1]++;
		}
	      efa = ca[f->pos];
	      if (efa & HI_MASK)
		{
		  attrbuff[attrlen++] = TY_HILIGHT;
		  if ((efa & HI_MASK) == HI_BLINK)
		    attrbuff[attrlen++] = 0xF1;
		  else if ((efa & HI_MASK) == HI_REVERSE)
		    attrbuff[attrlen++] = 0xF2;
		  else if ((efa & HI_MASK) == HI_UNDER)
		    attrbuff[attrlen++] = 0xF4;
		  else
		    attrbuff[attrlen++] = 0;
		  attrbuff[1]++;
		}
	      if (extcolor && (efa & EXT_MASK))
		{
		  attrbuff[attrlen++] = TY_FORE;
		  attrbuff[attrlen++] = efa & EXT_MASK | 0xF0 ;
		  attrbuff[1]++;
		}
	      /*...We don't support any other attribute types */
	    }
	  net_put(attrbuff, attrlen);
	  
	  /* Now send the data for this field */
	  last_ca_sent = DEFAULT_CA;
	  if (f->len > 1)
	    sendscr(f->pos + 1, f->pos + f->len >= scrsize ?
		    scrsize - f->pos - 1 : f->len - 1);

	  if (f->pos + f->len >= scrsize) break;
	  f = f->next;
	}
    }
  net_eor();
}


/* Send modified data fields.  If "all" is true, do a read modified all.  */
static void sendmodified(all)
     int all;
{
  register field *f, *g;
  int pos;

  /* Test Request Read heading */
  static unsigned char treq_heading[] =
    {
      0x01,			/* SOH */
      0x6C,			/* % */
      0x61,			/* / */
      0x02			/* STX */
    };

  /* For CLEAR or PA keys, do a short read (unless processing RMA command) */
  if (aid >= 0x6B && aid <= 0x6E && !all)
    {
      net_put(&aid, 1);
      net_eor();
      return;
    }

  /* If this is SYS REQ or Test REQ, send the Test Request Read heading */
  if (aid == AID_SYSREQ) 
    net_put(treq_heading, sizeof treq_heading);

  /* Otherwise send AID code followed by cursor address */
  else sendaddr(aid, cursor);

  /* If screen is unformatted, just send it with nulls suppressed */
  if (!curfield) 
    {
      int len;
      register unsigned char *bp;

      last_ca_sent = DEFAULT_CA;
      for (pos = 0; pos < scrsize; pos += len)
	{
	  /* Find the next non-null character */
	  while (pos < scrsize && screen[pos] == 0) pos++;
	  if (pos >= scrsize) break;

	  /* Look for next null */
	  bp = (unsigned char *) memchr(&screen[pos], 0, scrsize - pos);
	  if (!bp) bp = &screen[scrsize];

	  /* Send the non-null data */
	  len = bp - &screen[pos];
	  sendscr(pos, len);
	}
      newpos(0);
      net_eor();
      return;
    }

  /* Find the field with minimum position */
  pos = scrsize;
  for (g = f = curfield;;) 
    {
      if (f->pos < pos) 
	{
	  pos = f->pos;
	  g = f;
	}
      f = f->next;
      if (f == curfield) break;
    }
  
  /* Send all the modified fields */
  for (f = g;;)
    {
      if (screen[f->pos] & ATTR_MDT) sendfield(f);
      f = f->next;
      if (f == g) break;
    }
  net_eor();

  /* Note:  The manual says that the data stream sent inbound for
     a Test Request Read should end with an ETX.  However, other
     terminal emulators don't send the ETX and neither does a real
     3279 terminal, so I won't send it either.  */
}


/* Send a single field */
static void sendfield(f)
     register field *f;
{
  int startpos;
  register int pos, tpos;

  startpos = nextpos(f->pos);
  sendaddr(ORD_SBA, startpos);
  if (f->len <= 1) return;

  /* Scan backward over trailing nulls */
  pos = f->pos + f->len - 1;
  if (pos >= scrsize) pos -= scrsize;
  while (!screen[pos]) 
    {
      if (pos == startpos) return;
      pos = prevpos(pos);
    }
  
  /* Perform TSO adjustment:  If the field has nulls from its
     beginning to the end of the line, suppress them rather than
     turning them into blanks.  */

  tpos = startpos;
  while (tpos != pos)
    {
      if (screen[tpos]) break;
      tpos = nextpos(tpos);
      if (tpos % cols == 0)
	startpos = tpos;
    }
  
  /* Send the field, converting non-trailing nulls to spaces */
  last_ca_sent = DEFAULT_CA;
  if (pos < startpos) 
    {
      putcnvnull(startpos, scrsize - startpos);
      startpos = 0;
    }
  putcnvnull(startpos, pos - startpos + 1);
}


/* Send a buffer address */
static void sendaddr(code, addr)
     unsigned char code;
     int addr;
{
  unsigned char buff[3];
  buff[0] = code;

  /* If screen is small enough, use 12-bit addressing */
  if (scrsize < 4096) 
    {
      buff[1] = addrconv[addr >> 6];
      buff[2] = addrconv[addr & 0x3F];
    }
  /* Otherwise, use 14-bit addressing */
  else 
    {
      buff[1] = addr >> 8;
      buff[2] = addr & 0xFF;
    }
  net_put(buff, 3);
}


/* Do a sendscr, converting nulls to spaces */
static void putcnvnull(pos, len)
     register int pos, len;
{
  register unsigned char *bp;
  for (bp = &screen[pos];;)
    {
      bp = (unsigned char *) memchr(bp, 0, len - (bp - &screen[pos]));
      if (!bp) break;
      *bp++ = EBCSPACE;
    }
  sendscr(pos, len);
}


/* Send data from screen, checking for graphic escape orders.  Also,
   if the reply mode is set to character mode, insert SA orders if
   non-default character attributes are present.

   The "last_ca_sent" variable is used to keep track of changes to
   character attributes.  The caller must set it to DEFAULT_CA at the
   start of the buffer or field.  */

static void sendscr(pos, len)
     register int pos, len;
{
  register int cpos;
  char cval;
  static unsigned char
    ge[] =      { ORD_GE, 0 },
    sa_fore[] = { ORD_SA, TY_FORE, 0 },
    sa_hili[] = { ORD_SA, TY_HILIGHT, 0 };

  /* If no CAs are present then just send the data */
  if (!capresent) net_put(&screen[pos], len);	  

  /* If reply mode isn't set to character mode, just look for GEs */
  else if (reply_mode != RM_CHAR)
    while (len > 0) 
      {
	for (cpos = pos; cpos < pos + len && !(ca[cpos] & GRAPHIC); cpos++);
	if (cpos != pos)
	  {
	    net_put(&screen[pos], cpos - pos);	  
	    len -= cpos - pos;
	    pos = cpos;
	  }
	if (len)
	  {
	    ge[1] = screen[pos];
	    net_put(ge, 2);
	    pos++;
	    len--;
	  }
      }  

  /* Must process both GEs and character attributes */
  else
    {
      Boolean hili_wanted, fore_wanted;

      hili_wanted = (memchr(reply_attrlist, TY_HILIGHT, reply_numattr) != 0);
      fore_wanted = (memchr(reply_attrlist, TY_FORE, reply_numattr) != 0);
      
      cval = 0;
      while (len > 0)
	{
	  /* Scan ahead for a GE or a change in character attribute */
	  for (cpos = pos; cpos < pos + len; cpos++)
	    {
	      if (ca[cpos] & GRAPHIC) break;

	      cval = ca[cpos] & (COLOR_MASK + HI_MASK);
	      if ((cval & HI_MASK) != (last_ca_sent & HI_MASK) &&
		  hili_wanted) break;
	      if ((cval & COLOR_MASK) != (last_ca_sent & COLOR_MASK) &&
		  fore_wanted) break;
	    }
      
	  /* Write out all the characters up to the GE or changed attribute */
	  if (cpos != pos)
	    {
	      net_put(&screen[pos], cpos - pos);	  
	      len -= cpos - pos;
	      pos = cpos;
	    }
	  if (len <= 0) break;

	  /* Send any changed attributes */
	  if ((cval & HI_MASK) != (last_ca_sent & HI_MASK) && hili_wanted)
	    {
	      if ((cval & HI_MASK) == HI_BLINK)        sa_hili[2] = 0xF1;
	      else if ((cval & HI_MASK) == HI_REVERSE) sa_hili[2] = 0xF2;
	      else if ((cval & HI_MASK) == HI_UNDER)   sa_hili[2] = 0xF4;
	      else				       sa_hili[2] = 0;
	      net_put(sa_hili, 3);
	    }
	  if ((cval & COLOR_MASK) != (last_ca_sent & COLOR_MASK) &&
	      fore_wanted)
	    {
	      sa_fore[2] = 0xF0 | cval & COLOR_MASK;
	      if (sa_fore[2] == 0xF0) sa_fore[2] = 0;
	      net_put(sa_fore, 3);
	    }
	  last_ca_sent = cval;

	  /* Send graphic escape code if necessary */
	  if (ca[cpos] & GRAPHIC)
	    {
	      ge[1] = screen[pos];
	      net_put(ge, 2);
	    }
	  else net_put(&screen[pos], 1);
	  pos++;
	  len--;
	}
    }  
}


/* Send an attention identifier and modified data fields */
static void sendaid(code)
     unsigned char code;
{
  aid = code;
  setlock(True);
  inserting = False;
  x_inserting(False);

  /* Send inbound graphic data if specified in a request block */
  graph_data(code);

  /* Send the AID, cursor address, and modified data fields */
  sendmodified(False);
}


/* Reset modified data tags */
static void reset_mdt()
{
  register field *f;
  if (!curfield) return;
  for (f = curfield;;) 
    {
      screen[f->pos] &= ~ATTR_MDT;
      f = f->next;
      if (f == curfield) break;
    }
}


/* Replace the character at current cursor position.  Return false if fail.  */
static Boolean replace(c, graphic)
     unsigned char c;
     char graphic;
{
  register field *f;
  f = cursfield();
  if (f)
    {
      if (cursor == f->pos || !(f->attrib & UNPROTECTED) || !insert(f))
	return False;
      screen[f->pos] |= ATTR_MDT;
    }
  else if (inserting) return False;
  screen[cursor] = c;
  ca[cursor] = DEFAULT_CA | graphic;
  if (graphic) capresent = True;
  trbuff[cursor] = graphic ? getoa[c] : etoa[c];
  if (f) writeit(cursor, 1, f->attrib, ca[f->pos]);
  else writeit(cursor, 1, 0, DEFAULT_CA);
  if (c != EBCSPACE) blank = False;
  newcursor(cursor + 1);
  if (f && cursor == f->next->pos) loc_autoskip();
  return True;
}


/* Handle a data key */
static void do_string(ge_table)
     unsigned char *ge_table;
{
  register int j;
  unsigned char c, track_buff[4];

  for (j = 1; j < keylen; j++)
    {
      if (ge_table) c = ge_table[curkey[j]];
      else c = 0;
      if (!replace(c ? c : atoe[curkey[j]], c ? GRAPHIC : (char) 0)) BOOP;
    }
  if (!ga_support) return;
  
  /* If GA has graphics input enabled, read the track register */
  if (!ga_key(track_buff)) return;
  for (j = 0; j < 4; j++) 
    {
      c = track_buff[j];
      if (!replace(otoe[c], (char) ((!ga_sf && otoge[c]) ? GRAPHIC : 0))) BOOP;
    }
  sendaid(0x7D);
}


/* Erase to end of current field */
static void erasee()
{
  register field *f;
  register int pos, endpos;

  if (!(f = cursfield()))
    {
      memset(&screen[cursor], 0, scrsize - cursor);
      memset(&ca[cursor], DEFAULT_CA, scrsize - cursor);
      memset(&trbuff[cursor], ' ', scrsize - cursor);
      return;
    }
  if (!(f->attrib & UNPROTECTED) || cursor == f->pos) BOOP;
  screen[f->pos] |= ATTR_MDT;

  endpos = f->next->pos;
  for (pos = cursor; pos != endpos; pos = nextpos(pos))
    {
      screen[pos] = 0;
      ca[pos] = DEFAULT_CA;
      trbuff[pos] = ' ';
      writeit(pos, 1, f->attrib, ca[f->pos]);
    }
}


/* Erase all unprotected characters and re-position the cursor */
static void eraseu()
{
  register int j, k;
  register field *f;

  if (!curfield) 
    {
      clear_screen(alternate);
      return;
    }
  for (f = curfield;;) 
    {
      if (f->attrib & UNPROTECTED) 
	{
	  screen[f->pos] |= ATTR_MDT;
	  for (j = 1, k = nextpos(f->pos); j < f->len; j++, k = nextpos(k))
	    {
	      screen[k] = 0;
	      ca[k] = DEFAULT_CA;
	      trbuff[k] = ' ';
	      writeit(k, 1, f->attrib, ca[f->pos]);
	    }
	}
      f = f->next;
      if (f == curfield) break;
    }
  newcursor(0);
  loc_goinp();
}


/* Insert a character at current cursor position.  Return false if no room.  */
static Boolean insert(cursf)
     register field *cursf;
{
  register int pos, endpos;
  int len;
  
  if (!inserting) return True;
  endpos = cursf->next->pos;

  /* Look for a null to squash */
  for (pos = cursor; pos != endpos; pos = nextpos(pos))
    if (!screen[pos]) break;

  /* If no nulls, try for a trailing space */
  if (pos == endpos) 
    {
      pos = prevpos(endpos);
      if (screen[pos] != EBCSPACE) return False;
    }

  /* Figure out length of changed area */
  len = pos - cursor;
  if (len < 0) len += scrsize;
  
  /* Shift characters right */
  for (; pos != cursor; pos = prevpos(pos))
    {
      screen[pos] = screen[prevpos(pos)];
      ca[pos] = ca[prevpos(pos)];
      trbuff[pos] = trbuff[prevpos(pos)];
    }
  pos = nextpos(cursor);
  writewrap(pos, len, cursf->attrib, ca[cursf->pos]);
  screen[cursf->pos] |= ATTR_MDT;
  return True;
}


/* Delete character at current cursor position.  Return true if successful.  */
delete()
{
  register int j, pos, len;
  register field *cursf;

  cursf = cursfield();
  if (!cursf || cursor == cursf->pos || !(cursf->attrib & UNPROTECTED))
    return False;
  len = cursf->len - (cursor - cursf->pos + 1);
  if (len < 0) len += scrsize;
  else if (len >= scrsize) len -= scrsize;
  for (j = 0, pos = cursor; j < len; j++, pos = nextpos(pos)) 
    {
      screen[pos] = screen[nextpos(pos)];
      ca[pos] = ca[nextpos(pos)];
      trbuff[pos] = trbuff[nextpos(pos)];
    }
  screen[pos] = 0;
  ca[pos] = DEFAULT_CA;
  trbuff[pos] = ' ';
  writewrap(cursor, len + 1, cursf->attrib, ca[cursf->pos]);
  screen[cursf->pos] |= ATTR_MDT;
  return True;
}


/* Take input from the keyboard input queue until keyboard locks */
static void kflush()
{
  while (!locked)
    {
      keylen = q_get(&curkey);
      if (!keylen)
	{
	  api_unlock();
	  return;
	}
      (key_function[curkey[0]])();
    }
}


/* Set keyboard lock state */
static void setlock(val)
     Boolean val;
{
  if (locked && !val && q_empty()) api_unlock();
  locked = val;
  x_sys(val);
}


/* Dump fields for debugging */
static void dumpfields()
{
  int i;
  field *f, *oldf;
  if (!debug || !curfield) return;

  printf(" Pos  Len FA EFA          Text          ");
  if (capresent) printf("             CA\n");
  else printf("             Hex\n");

  f = curfield;
  for (i = 0; i < scrsize; i++)	/* Protect against infinite loops */
    {
      register int j, k;
      char s[21], cabuff[21];

      for (j = 0, k = nextpos(f->pos);
	   j < f->len - 1 && j < 20;
	   j++, k = nextpos(k))
	{
	  s[j] = trbuff[k];
	  if (s[j] < 32 || s[j] > 127) s[j] = ' ';
	  cabuff[j] = ca[k];
	}
      for (k = j; k < 20; k++) 
	{
	  s[k] = ' ';
	  cabuff[k] = 0;
	}
      s[20] = 0;

      printf("%4d %4d %2x %2x  |%s| ", f->pos, f->len, f->attrib,
	     ca[f->pos], s);
      for (j = 0; j < 10 && j < f->len - 1; j++)
	printf("%2x ", capresent ? cabuff[j] : s[j]);
      putchar('\n');

      oldf = f;
      f = f->next;
      if (f->prev != oldf) printf("<Bad prev link>\n");
      if (f == curfield) break;
    }
  putchar('\n');
}


/* Keyboard functions */
static void c_undefined()	{ BOOP; }
static void c_string()		{ do_string((unsigned char *) NULL); }
static void c_cursor_move()  	{ newcursor((int) ((curkey[1] << 8) + curkey[2]
				  + ((curkey[3] << 8) + curkey[4]) * cols)); }
static void c_up()		{ newcursor(cursor - cols); }
static void c_down()		{ newcursor(cursor + cols); }
static void c_left()		{ newcursor(cursor - 1); }
static void c_right()		{ newcursor(cursor + 1); }
static void c_newline()		{ newcursor(cursor - cursor % cols + cols);
				  loc_goinp(); }
static void c_home()		{ newcursor(0); loc_goinp(); }
static void c_tab()		{ loc_tab(); }
static void c_backtab()		{ loc_backtab(scrsize); }
static void c_end()		{ loc_end(screen, scrsize); }
static void c_forward_word()	{ loc_forward_word(screen); }
static void c_backward_word()	{ loc_backward_word(screen); }

static void c_insert()		{ inserting = !inserting;
				  x_inserting(inserting); }
static void c_delete()		{ if (!cursfield()) BOOP;
				  if (!delete()) BOOP; }
static void c_backspace()	{ newcursor(cursor - 1); (void) delete(); } 
static void c_kill_word()	{ if (!loc_kill_word(screen, scrsize)) BOOP; }
static void c_backward_kill_word() { if (!loc_backward_kill(screen)) BOOP; }

static void c_erase_eof()	{ erasee(); }
static void c_erase_input()	{ eraseu(); }
static void c_dup()		{ if (!replace(0x1C, 0)) BOOP; }
static void c_field_mark()	{ if (!replace(0x1E, 0)) BOOP; }
static void c_cent()		{ if (!replace(0x4A, 0)) BOOP; }

static void c_pf1()   		{ sendaid(0xF1); }
static void c_pf2()   		{ sendaid(0xF2); }
static void c_pf3()   		{ sendaid(0xF3); }
static void c_pf4()   		{ sendaid(0xF4); }
static void c_pf5()   		{ sendaid(0xF5); }
static void c_pf6()   		{ sendaid(0xF6); }
static void c_pf7()   		{ sendaid(0xF7); }
static void c_pf8()   		{ sendaid(0xF8); }
static void c_pf9()   		{ sendaid(0xF9); }
static void c_pf10()  		{ sendaid(0x7A); }
static void c_pf11()  		{ sendaid(0x7B); }
static void c_pf12()  		{ sendaid(0x7C); }
static void c_pf13()  		{ sendaid(0xC1); }
static void c_pf14()  		{ sendaid(0xC2); }
static void c_pf15()  		{ sendaid(0xC3); }
static void c_pf16()  		{ sendaid(0xC4); }
static void c_pf17()  		{ sendaid(0xC5); }
static void c_pf18()  		{ sendaid(0xC6); }
static void c_pf19()  		{ sendaid(0xC7); }
static void c_pf20()  		{ sendaid(0xC8); }
static void c_pf21()  		{ sendaid(0xC9); }
static void c_pf22()  		{ sendaid(0x4A); }
static void c_pf23()  		{ sendaid(0x4B); }
static void c_pf24()  		{ sendaid(0x4C); }

static void c_clear() 		{ graph_delete(); clear_screen(False);
				  reply_mode = RM_FIELD; sendaid(0x6D); }
static void c_enter() 		{ sendaid(0x7D); }
static void c_pa1()   		{ sendaid(0x6C); }
static void c_pa2()   		{ sendaid(0x6E); }
static void c_pa3()   		{ sendaid(0x6B); }
static void c_sysreq()		{ sendaid(AID_SYSREQ); }
static void c_attn()		{ net_break(); }

static void c_reset()		{ setlock(False); inserting = False;
				  x_inserting(False); q_clear(); } 
static void c_unlock()		{ setlock(False); kflush(); } 
static void c_dumpfields()	{ dumpfields(); }
static void c_print_screen()	{ x_print_screen(); }

static void c_stringa()		{ do_string(atoge_a); }
static void c_stringa_()	{ do_string(atoge_a_); }

static void c_hex()		{ int j; for (j = 1; j < keylen; j++)
				    if (!replace(curkey[j], (char) 0)) BOOP; }
     
static void c_hex_ge()		{ int j; for (j = 1; j < keylen; j++)
				    if (!replace(curkey[j], GRAPHIC)) BOOP; }

