/*
 * Atari ST-specific functions
 * written by Rob Janssen, PE1CHL
 * 3 configurations are possible:
 *	.TOS
 *	.ACC	define GEM and GEMACC
 *	.PRG	define GEM and GEMPRG
 */

#include <stdio.h>
#include <ctype.h>
#ifdef MWC
# include <osbind.h>			/* os interface defines */
# define Kbshift	Getshift	/* MW goofed... */
#endif
#ifdef __TURBOC__
# define cdecl
# include <tos.h>			/* os interface defines */
#endif
#include <linea.h>			/* line-A interface defines */
#include <time.h>

#ifdef __TURBOC__			/* line-A access for Turbo C */
# define VPLANES	Linea->v_planes
# define V_CEL_HT	Vdiesc->v_cel_ht
# define V_CEL_MX	Vdiesc->v_cel_mx
# define V_CEL_MY	Vdiesc->v_cel_my
# define V_CUR_AD	((char *) (Vdiesc->v_cur_ad))
# define V_CUR_OFF	Vdiesc->v_cur_off
# define V_CUR_CX	Vdiesc->v_cur_xy[0]
# define V_CUR_CY	Vdiesc->v_cur_xy[1]
# define V_FNT_AD	((char *) (Vdiesc->v_fnt_ad))
#endif

#include "global.h"
#include "config.h"
#include "mbuf.h"
#include "internet.h"
#include "iface.h"
#include "atari.h"
#include "timer.h"
#include "environ.h"

extern unsigned nasy;

/* Interface list header */
struct interface *ifaces;

#ifdef OUTBUF
static char *ttbuf;
#endif

int  auxcon = 0;			/* auxcon control word */
#define AUXCOUT		0x01		/* send out to AUX */
#define AUXCNOT		0x02		/* don't send out to screen */
#define AUXCIN		0x10		/* read input from AUX */
int  auxitrn[256];			/* aux input translate table */
char auxotrn[256];			/* aux output translate table */

int  screenmode = 0;			/* screen output control word */
#define SCRTOS		0		/* output via TOS */
#define SCRBIOS		1		/* output via BIOS */
#define SCRDIR		2		/* direct output to screen */

extern void trap_x();			/* handler for trap #5/#11 */
void (*trap_x_save)();			/* saved trap #5/#11 handler */

extern char startup[];			/* startup file name */

extern FILE *logfp;			/* log file pointer */
extern char logname[];			/* log file name */

extern FILE *trfp;			/* trace file pointer */
extern char trname[];			/* trace file name */

#ifdef GEM

#define	 WORD		int		/* signed word (16 bits) */
#define	 TRUE		(1)		/* TRUE	 value */
#define	 FALSE		(0)		/* FALSE value */

#include <gemdefs.h>			/* MW GEM definitions */
#include <obdefs.h>

#define	 MAXWIN		8		/* maximal window handle from GEM */
#define	 USEWIN		2		/* windows used by this application */

#define	 DESK		0		/* window number for desktop */
#define	 ALIGN(x,n)	(x) = (n * (((x) + (n / 2)) / n))/* align x to n incr. */

#define	 WI_KIND	(HSLIDE|RTARROW|LFARROW|VSLIDE|DNARROW|UPARROW|SIZER|MOVER|FULLER|CLOSER|NAME)

#define	 MIN_WIDTH	(16 * gr_hwbox) /* minimal window width */
#define	 MIN_HEIGHT	(5 * gr_hhbox)	/* minimal window height */

#define	 MAXLINE	100		/* history lines saved */
#define	 MAXCHAR	80		/* max characters on each line */

/*** datatypes ***/

typedef struct linehist			/* define 'line history' type */
{
   struct linehist *next;		/* forward link */
   struct linehist *prev;		/* backward link */
   int	len;				/* this one's length */
   char line[1];			/* the actual line to be stored */
} LINEHIST;

typedef struct				/* define 'sliding window' type */
{
   long total;				/* total size in this dimension */
   long pos;				/* pos of portion currently seen */
   int	seen;				/* size of portion currently seen */
} SLWIN;

typedef struct window			/* info about windows */
{
   WORD	 win;				/* corresponding window handle */
   WORD	 kind;				/* this window's wi_kind */
   WORD	 pobj;				/* object selected there */
   GRECT work;				/* work area pos/size */
   GRECT full;				/* full size of window */
   GRECT org;				/* originating pos/size for shrink */
   SLWIN hori;				/* horizontal slider info */
   SLWIN vert;				/* vertical slider info */
   LINEHIST *head;			/* head of line history */
   LINEHIST *tail;			/* tail of line history */
   int	 curpos;			/* position on current line */
   char	 curline[MAXCHAR + 1];		/* current line */
   char	 cursor;			/* cursor state */
   char	 open;				/* window opened? */
   char	 title[MAX_LEN];		/* window title (if any) */
   char	 info[MAX_LEN];			/* window info (if any) */
} WINDOW;

/*** GLOBAL VARIABLES ***/

/* GEM handles, information and the like */

extern WORD gl_apid;			/* application id (external to MWC) */
WORD gl_mnid;				/* menu id for this accessory */
WORD gr_handle;				/* physical workstation handle */
static WORD handle;			/* virtual workstation handle */

WORD gr_hhchar,gr_hwchar,gr_hhbox,gr_hwbox;/* system font sizes */

/* window management datastructures */

static WINDOW window[USEWIN];		/* window info, DESK + user windows */
static WORD win[MAXWIN];		/* handle to application conv. */
static WORD topwin = -1;		/* top (=current) window handle */

static WORD await = MU_MESAG|MU_TIMER;	/* select initially active events */
static GRECT m1,m2;			/* mouse event rectangles */
static WORD m1flag,m2flag;		/* flags for mouse events 1,2 */

static WORD keyqueue = 0;		/* last typed character */
static WORD keyqstat = 0;		/* it's shift status */
static WORD fastpoll = 50;		/* fast poll interval */

WORD work_in[] = {1,1,1,1,1,1,1,1,1,1,2};/* Input to GSX parameter array */
WORD work_out[57];			/* Output from GSX parameter array */

WORD contrl[12];			/* storage wasted for bindings */
WORD intin[128];
WORD ptsin[128];
WORD intout[128];
WORD ptsout[128];

/* the resource file symbols */

#ifdef GEMPRG
# include "atari_rs.h"
extern OBJECT *rs_trindex[];
#endif

/* forward declarations (when necessary) */

void pop_up();
void wi_getwork();
void wi_close();
void sl_update();
void rq_redraw();
WORD do_form();
WORD *rc2pts();
int  wputc();

#else					/* not GEM */

# define CONTERM *((char *) 0x484L)	/* system's conterm byte */
char	sconterm;			/* saved conterm byte */

char *logbase;				/* logical screen base */

int	cursor = 1;			/* cursor currently visible? */
# ifdef MWC
extern	int _fputt();			/* standard output putc routine */
# endif
#endif

#ifndef GEMACC
# ifdef MWC
long *_stksize = (long *) 4096;		/* stack size in bytes, becomes ptr */
#  define STACK _stksize
# endif
# ifdef __TURBOC__
extern long *_StkLim;			/* initialized to stack limit */
#  define STACK _StkLim
# endif

# define STKMAGIC 0x31415927L		/* magic to check overflow */
#endif

void eihalt();

#ifndef GEMACC
#ifdef __TURBOC__
static void watchtick(void,...);	/* function called at timer tick */
static void (*orgtick)(void,...);	/* original timertick handler */
#else
static void watchtick();		/* function called at timer tick */
static void (*orgtick)();		/* original timertick handler */
#endif
static long watchcnt;			/* actual watchdog counter */
static long watchnet;			/* watchdog while in NET */
static long watchsh;			/* watchdog while in SHELL */
static unsigned watchhw;		/* watchdog hardware reset */
#endif

static void lat_start(),lat_stop(),lat_raw(),lat_status();


/* Called at startup time to set up console I/O, memory heap */
/* also sets and saves lots of other things on the Atari.... */

ioinit()
{
#ifdef GEM
	WORD d;
# ifdef GEMACC
	char net[30];
# endif
#else
	long sav_ssp;			/* saved stackpointer for Super() */
#endif

#if (defined(MWC) && (MWC < 300))	/* pre-3.0 MW C? */
	/* set the handler for trap #5. this will be called using the */
	/* micro_rtx() function, and will disable/enable interrupts */
	trap_x_save = (void (*)()) Setexc(0x25,trap_x);
#else
	/* set the handler for trap #7. this will be called using the */
	/* _VtrapB_() function, and will disable/enable interrupts */
	trap_x_save = (void (*)()) Setexc(0x27,trap_x);
#endif

#ifndef GEMACC
	STACK[0] = STACK[16] =		/* mark stack for overflow check */
	STACK[32] = STKMAGIC;

	orgtick = (void (*)()) Setexc(0x100,watchtick); /* install watchdog ticker */
#endif

#if (OUTBUF && !GEM)
	ttbuf = malloc(BUFSIZ);
	setbuf(stdout,ttbuf);
#endif

#ifdef GEM
	/* re-route output from "stdout" to window handler */

	stdout->_pt = wputc;		/* character output handler */
	stdout->_ff |= _FERR;		/* to disable fflush() */

	if (!appl_init() || gl_apid < 0) /* init application */
		exit(1);		/* exit if error (fatal) */

	gr_handle = graf_handle(&gr_hwchar,&gr_hhchar,&gr_hwbox,&gr_hhbox);

	handle = gr_handle;		/* open virtual workstation */
	v_opnvwk(work_in,&handle,work_out); /* handle:=virtual ws handle */

	vst_alignment(handle,0,5,&d,&d); /* set text alignment */

# ifdef GEMPRG
	rsrc_fixup();			/* fix the in-memory resource */
	menu_bar(rs_trindex[MENUBAR],TRUE); /* show the menu bar */
# endif

# ifdef GEMACC
	if ((gl_mnid = menu_register(gl_apid, "  KA9Q TCP/IP Net ")) < 0)
		exit(2);
# endif

	window[0].win = DESK;		/* init desktop window descr. */
	window[0].open = TRUE;
	wi_getwork(0);			/* get workarea of desktop */
	win[DESK] = 0;			/* remember it's handle */

# ifdef GEMPRG
	rs_trindex[BACKDROP][ROOT].ob_x = window[DESK].work.g_x;/* set size to full desktop */
	rs_trindex[BACKDROP][ROOT].ob_y = window[DESK].work.g_y;
	rs_trindex[BACKDROP][ROOT].ob_width = window[DESK].work.g_w;
	rs_trindex[BACKDROP][ROOT].ob_height = window[DESK].work.g_h;

	ob_draw(DESK,rs_trindex[BACKDROP],ROOT); /* draw the backdrop */

	wind_set(DESK,WF_NEWDESK,rs_trindex[BACKDROP],ROOT);
# endif

	window[1].hori.total = MAXCHAR; /* init window 1 description */
	window[1].vert.total = 1;	/* always one current line... */

	m1.g_x = m1.g_y = m1.g_w = m1.g_h =  /* no mouse event #1 */
	m2.g_x = m2.g_y = m2.g_w = m2.g_h =  /* no mouse event #2 */
	m1flag = m2flag = 0;		/* dummy flag (entry) */

# ifdef GEMACC
	for (d = 0; d < 50; d++)	/* allow others to initialize */
		eihalt();
# endif

# ifdef GEMPRG
	pop_up();
	graf_mouse(ARROW);		/* default mouse form */
# endif

# ifdef GEMACC
	if ((d = Dgetdrv()) >= 2)	/* a harddisk system? (??) */
	{
	   strcpy(net,startup);		/* find startup file */

	   do
	   {
	      net[0] = d + 'a';		/* try C-P */
	   } while (access(net,4) && ((++d < 16) || (d = 0)));

	   Dsetdrv(d);			/* move to where it's found */
	}
# endif
#else					/* not GEM */
	sav_ssp = Super(NULL);		/* switch to supervisor mode */
	sconterm = CONTERM;		/* read present console flags */
	CONTERM |= 0x08;		/* set the "return shift state" flag */
	Super(sav_ssp);			/* switch-back to user mode */
#endif

	atari_init('n');		/* general initialization */
}

/* Called just before exiting to restore console state (and others)
 */

iostop()
{
	long sav_ssp;			/* saved stackpointer for Super() */
#ifdef GEMPRG
	WORD wi;

	graf_mouse(HOURGLASS);		/* revert to 'wait' mouseform */

	for (wi = 1; wi < MAXWIN; wi++) /* close the windows */
	    if (win[wi] != 0)		/* when they are in use */
		wi_close(wi);

	menu_bar(rs_trindex[MENUBAR],FALSE); /* remove the menubar */
	v_clsvwk(handle);		/* close virtual workstation */
	appl_exit();			/* exit from application */
#endif

#if (OUTBUF && !GEM)
	setbuf(stdout,NULLCHAR);
	free(ttbuf);
#endif
	while(ifaces != NULLIF){
		/* first flush the interface's buffers */
		while(ifaces->recv != NULLVFP && (*ifaces->recv)(ifaces))
			;
		if(ifaces->stop != NULLFP)
			(*ifaces->stop)(ifaces);
		ifaces = ifaces->next;
	}

#ifndef GEM
	while (kbread() != -1)		/* eat remaining chars, cursor ON */
		;

	sav_ssp = Super(NULL);		/* switch to supervisor mode */
	CONTERM = sconterm;		/* restore value saved at startup */
	Super(sav_ssp);			/* switch-back to user mode */
#endif

	atari_stop();			/* general termination */

	lat_stop();			/* stop latency measurement */

#ifndef GEMACC
	Setexc(0x100,orgtick);		/* restore original timer tick */
#endif
#if (defined(MWC) && (MWC < 300))	/* pre-3.0 MW C? */
	Setexc(0x25,trap_x_save);	/* remove our trap #5 handler */
#else
	Setexc(0x27,trap_x_save);	/* remove our trap #7 handler */
#endif

	sav_ssp = Super(NULL);		/* switch to supervisor mode */
	*((long *) 0x3fc) = 0x0L;	/* erase signature (see log proc) */
	Super(sav_ssp);			/* switch-back to user mode */
}

/* checks the time then ticks and updates ISS */
void
check_time()
{
	int32 iss();
	int maxticks = (int) SEC2TICK(15); /* upper limit on delayed ticks */
	static unsigned long clkval = 0;

#ifndef NOCLOCK
	clock_t clknow = clock() / 20L; /* ANSI routine to read clock */
#else
	long sav_ssp;			/* saved stackpointer for Super() */
	unsigned long clknow;		/* current clock value */

	sav_ssp = Super(NULL);		/* switch to supervisor mode */
	clknow = *((unsigned long *) 0x4baL) / 20L; /* read 200Hz timer value, make 10/sec */
	Super(sav_ssp);			/* switch-back to user mode */
#endif

	if (clkval == 0)		/* first time? */
		clkval = clknow;

	while(clkval < clknow){
#ifdef IP
		icmpclk();		/* Call this one before tick */
#endif
		tick();
#ifdef IP
		(void)iss();
#endif
		if (maxticks--)		/* check a limit on ticks */
			clkval++;
		else
			clkval = clknow; /* thereafter, ignore them */
	}
}

#ifdef MWC
/* Mark Williams C reads the keyboard clock on the first call to time() */
/* using the Kgettime() routine. Unfortunately, this clock has moved to */
/* a different place in the Mega ST, so we'll redefine Kgettime to call */
/* the xbios routine that will read both the clocks (MW should do that!)*/

tm_t *Kgettime ()

{
   static tm_t rv;			/* returned value is static struct */
   register tetd_t tim;			/* value from xbios */

   tim = Gettime();			/* reads keyboard or RTC chip */

   rv.tm_sec = (tim & 0x1f) * 2;	/* silly 2-second time increment */
   rv.tm_min = (tim >> 5) & 0x3f;
   rv.tm_hour = (tim >> 11) & 0x1f;
   rv.tm_mday = (tim >> 16) & 0x1f;
   rv.tm_mon = ((tim >> 21) & 0x0f) - 1;
   rv.tm_year = ((tim >> 25) & 0x7f) + 80;
   /* note: tm_wday, tm_yday and tm_isdst not set */

   return (&rv);
}
#endif

/* Read characters from the keyboard, translating them to "real" ASCII
 * If none are ready, return -1
 */
int
kbread()
{
	unsigned char key,scan,shift;
	int  c;
	static int altkeys = 0;		/* for alt-digit pressure */
	static int altchar = 0;
#ifdef GEM
	int  x,y;
	register WINDOW *curwin;
	char outstr[4];
#else
	long keychar;
#endif

	if (auxcon & AUXCIN)		/* check the aux port for chars? */
	{
	   if (Bconstat(1))		/* a character ready? */
	      return (auxitrn[uchar(Bconin(1))]); /* read it and translate */
	}

#ifdef GEM
	if (!keyqueue)			/* no char typed? */
	{
		if (topwin > 0 && win[topwin])	/* our window on top? */
		{
			curwin = &window[win[topwin]];

			if (curwin->open && !curwin->cursor &&
			    curwin->curpos >= curwin->hori.pos &&
			    (y = curwin->vert.total -
				 curwin->vert.pos - 1) <= curwin->vert.seen)
			{
				vs_clip(handle,TRUE,rc2pts(&curwin->work));

				x = curwin->work.g_x + 1 +
					(curwin->curpos - curwin->hori.pos) * gr_hwchar;

				y = curwin->work.g_y + y * gr_hhchar;

				outstr[0] = 240;	/* cursor char */
				outstr[1] = ' ';
				outstr[2] = '\0';
				v_gtext(handle,x,y,outstr);

				curwin->cursor = 1;
			}
		}

		return (-1);
	}

	key = keyqueue;			/* get ascii part */
	scan = keyqueue >> 8;		/* get scancode */
	shift = keyqstat & 0x1f;	/* get shift status */
	keyqueue = keyqstat = 0;	/* keycode used, clear it */
#else
	if (!Cconis()){			/* any char available? */
		if (!(Kbshift(-1) & 0x08)){ /* no, Alternate not pressed? */
			if (altkeys){
				c = altchar;
				altchar = altkeys = 0;
				return (c);
			}
		}
		if (!cursor){		/* cursor visible? */
			cursor = 1;
			Bconout(2,'\033'); /* no, switch it on */
			Bconout(2,'e');
		}
		return (-1);		/* return -1 for 'no chars' */
	}

	keychar = Crawcin();		/* read char, no echo */
	key = keychar;			/* get ascii part */
	scan = keychar >> 16;		/* get scancode */
	shift = (keychar >> 24) & 0x1f; /* get shift status */
#endif

	if ((shift & 0x08) &&		/* Alternate key pressed? */
	    scan >= 103 && scan <= 112 && /* numeric keypad entry */
	    key >= '0' && key <= '9')	/* more verification of numeric */
	{
		altchar = 10 * altchar + key - '0'; /* compute code */

		if (++altkeys == 3){	/* code complete? */
			c = altchar;
			altchar = altkeys = 0;
			return (c);
		} else {
			return (-1);	/* incomplete, return no char */
		}
	}

	altchar = altkeys = 0;		/* no Alt - wipe Alternate char */

	if((c = key) == 0){
		/* a special char */
		switch(scan)		/* examine scancode */
		{
		case 3:			/* NULL (bizzare!) */
			c = 0;
			break;
		case 59:		/* F-1 key */
			c = -11;
			break;
		case 60:		/* F-2 key */
			c = -10;
			break;
		case 61:		/* F-3 key */
			c = -9;
			break;
		case 62:		/* F-4 key */
			c = -8;
			break;
		case 63:		/* F-5 key */
			c = -7;
			break;
		case 64:		/* F-6 key */
			c = -6;
			break;
		case 65:		/* F-7 key */
			c = -5;
			break;
		case 66:		/* F-8 key */
			c = -4;
			break;
		case 67:		/* F-9 key */
			c = -3;
			break;
		case 68:		/* F-10 key (used as command-mode escape) */
			c = -2;
			break;
		case 71:		/* HOME key */
			c = -20;
			break;
		case 72:		/* UP arrow */
			c = -19;
			break;
		case 73:		/* PAGE UP */
		case 98:		/* HELP key (used as PAGE UP) */
			c = -18;
			break;
		case 75:		/* LEFT arrow */
			c = -17;
			break;
		case 77:		/* RIGHT arrow */
			c = -16;
			break;
		case 119:		/* END (ctrl-Home) key */
			c = -15;
			break;
		case 80:		/* DOWN arrow */
			c = -14;
			break;
		case 81:		/* PAGE DOWN */
		case 97:		/* UNDO key (used as PAGE DOWN) */
			c = -13;
			break;
		case 82:		/* INS key */
			c = -12;
			break;
		case 83:		/* DEL key */
			c = 0x7f;
			break;
		default:		/* Dunno what it is */
			c = -1;
		}
	}

	return (c);
}

#ifdef GEM
/* utility routines only needed for GEM version */

/*** elementary window management routines ***/

/* align window in such a way that first character inside it starts */
/* on a byte boundary.	a somewhat specialized function... */

void wi_align (rc)
   register GRECT *rc;			/* window outside dimensions */

{
   /* first, calculate work area */

   wind_calc(1,WI_KIND,rc->g_x,rc->g_y,rc->g_w,rc->g_h,
		      &rc->g_x,&rc->g_y,&rc->g_w,&rc->g_h);

   ALIGN(rc->g_x,8);			/* align on 8-bit boundary */

   if (--(rc->g_x) < 0)			/* one bit extra, left of char */
      rc->g_x += 8;			/* if that makes it negative, bump */

   ALIGN(rc->g_w,gr_hwchar);		/* integer number of chars wide */
   rc->g_w++;				/* one extra (left of first char) */

   ALIGN(rc->g_h,gr_hhchar);		/* integer number of lines high */

   /* re-calculate outside dimensions */

   wind_calc(0,WI_KIND,rc->g_x,rc->g_y,rc->g_w,rc->g_h,
		      &rc->g_x,&rc->g_y,&rc->g_w,&rc->g_h);
}

/* create and open a window, and update window datatructure */
/* return TRUE when the window could be created and opened  */
/* assumes some fields of window[wi_num] already set up:    */
/*   title,info,org					    */

int  wi_open (wi_num,kind,wsize)
   WORD wi_num;				/* window NUMBER */
   WORD kind;				/* window kind */
   register GRECT *wsize;		/* desired pos/size rc */

{
   register WINDOW *curwin = &window[wi_num];
   GRECT rc;
   WORD pts[4];				/* dummy for clip_off */

   if (!curwin->open)			/* not yet open? */
   {
      curwin->kind = kind;		/* remember kind of window */

      graf_growbox(curwin->org.g_x,curwin->org.g_y,
		   curwin->org.g_w,curwin->org.g_h,
		   wsize->g_x,wsize->g_y,wsize->g_w,wsize->g_h);

      rc_copy(&window[0].work,&curwin->full);/* max size is desksize */
      wi_align(&curwin->full);		/* but, aligned to char bound */

      curwin->full.g_y = window[0].work.g_y +
			(window[0].work.g_h - curwin->full.g_h) / 2;

      while (curwin->full.g_x + curwin->full.g_w >
	     window[0].work.g_x + window[0].work.g_w)
	 curwin->full.g_w -= gr_hwchar; /* make sure it will fit on desktop */

      if ((curwin->win = wind_create(curwin->kind,curwin->full.g_x,
						  curwin->full.g_y,
						  curwin->full.g_w,
						  curwin->full.g_h)) < 0)
      {
	 form_alert(1,"[3][|No more windows available][ SORRY ]");
	 return (FALSE);
      }

      if (curwin->win >= MAXWIN)	/* unexpected window number? */
      {
	 form_alert(1,"[3][|Unexpected window handle][ SORRY ]");
	 wind_delete(curwin->win);	/* destroy bad window */
	 return (FALSE);
      }

      win[curwin->win] = wi_num;	/* remember number for handle */

      wind_set(curwin->win,WF_NAME,curwin->title);/* effectuate title */
      wind_set(curwin->win,WF_INFO,curwin->info);/* effectuate info */

      wind_set(curwin->win,WF_HSLSIZE,1000);/* initially, full sliders */
      wind_set(curwin->win,WF_VSLSIZE,1000);

      wind_update(BEG_UPDATE);		/* lock rectangle list */

      wind_open(curwin->win,wsize->g_x,wsize->g_y,/* actually open the window */
			    wsize->g_w,wsize->g_h);

      curwin->open = TRUE;		/* remember it is open */
      topwin = curwin->win;		/* it always comes on top */

      wi_getwork(curwin->win);		/* get size/pos info */
      sl_update(curwin->win);		/* set sliders */

      rc_copy(&curwin->work,&rc);	/* get window workarea */
      if (rc_intersect(&window[0].work,&rc))/* intersect with desktop */
      {
	 vs_clip(handle,FALSE,pts);	/* clip off for speed */
	 vsf_interior(handle,0);	/* blank the work area */
	 vr_recfl(handle,rc2pts(&rc));
      }

      wind_update(END_UPDATE);		/* unlock rectangle list */
   }

   return (TRUE);			/* results okay! */
}

/* close a window and update window datatructure */

void wi_close (wi)
   WORD wi;				/* window handle */

{
   register WINDOW *curwin = &window[win[wi]];
   WORD d;				/* dummy for wind_get */

   if (curwin->open)			/* now open? */
   {
      wind_close(wi);			/* close the window */

      graf_shrinkbox(curwin->org.g_x,curwin->org.g_y,
		     curwin->org.g_w,curwin->org.g_h,
		     curwin->work.g_x,curwin->work.g_y,
		     curwin->work.g_w,curwin->work.g_h);

      if (wi == topwin)			/* was it the top window? */
	 wind_get(0,WF_TOP,&topwin,&d,&d,&d);/* get new top window */

      wind_delete(wi);			/* delete window */

      curwin->open = FALSE;		/* remember it is closed */
      win[wi] = 0;			/* also forget the handle */
   }
}

/* get a window's current size and position */
/* also sets amount seen for sliding scrollbars */

void wi_getwork (wi)
   WORD wi;

{
   register WINDOW *curwin = &window[win[wi]];

   wind_get(wi,WF_WORKXYWH,		/* get size and position info */
	    &curwin->work.g_x,&curwin->work.g_y,
	    &curwin->work.g_w,&curwin->work.g_h);

   /* set the 'amount seen' for each window */

   switch (win[wi])			/* determine application usage */
   {
   case 1:				/* output window */
      curwin->hori.seen = curwin->work.g_w / gr_hwchar;
      curwin->vert.seen = curwin->work.g_h / gr_hhchar;
      break;
   }
}

/* window character-handling routines */

/* linefeed adds another line to the history, and updates window (scroll) */

static void linefeed (curwin)
	register WINDOW *curwin;

{
   register LINEHIST *new;

   if (curwin->vert.total > MAXLINE)	/* above the maximum? */
   {
      if (curwin->head != NULL)		/* should be! */
      {
	 free(curwin->head);		/* release it */
	 curwin->head = curwin->head->next;
	 curwin->head->prev = NULL;
	 curwin->vert.total--;
      }
   }

   curwin->curline[curwin->curpos] = '\0';

   if ((new = malloc(sizeof(LINEHIST) + curwin->curpos)) == NULL)
      return;				/* that's pity! */

   new->len = curwin->curpos;
   strcpy(new->line,curwin->curline);

   if (curwin->tail == NULL)
   {
      curwin->head = curwin->tail = new;
      new->prev = new->next = NULL;
   }
   else
   {
      new->prev = curwin->tail;
      new->next = NULL;
      curwin->tail->next = new;
      curwin->tail = new;
   }

   curwin->vert.total++;

   if (curwin->vert.pos >= curwin->vert.total - curwin->vert.seen - 1)
      curwin->vert.pos = curwin->vert.total - curwin->vert.seen;

   curwin->curpos = 0;

   sl_update(curwin->win);		/* update sliders */

   if (curwin->vert.pos >= curwin->vert.total - curwin->vert.seen)
   {
      rq_redraw(curwin->win,&curwin->work); /* draw desired window */
      eihalt();				/* handle messages */
   }
}

/* write a character to a window */

int wputc (c,outfile)
   int c;
   FILE *outfile;

{
   int	wdw;
   WORD x,y;
   register WINDOW *curwin;
   char outstr[2];

   if (outfile == stdout)		/* determine window# from handle */
   {
      if (auxcon & AUXCOUT)		/* standard output to AUX too? */
      {
	 if (c == '\n')			/* if \n, add \r */
	    Bconout(1,auxotrn['\r']);

	 Bconout(1,auxotrn[uchar(c)]);	/* send (translated) character */
      }

      if (auxcon & AUXCNOT)		/* not to screen? */
	 return;			/* then we're done! */

      wdw = 1;				/* also to window 1 */
   }
   else
      return (EOF);

   curwin = &window[wdw];		/* address output window */

   switch(c)				/* character interpreter */
   {
   case '\b': if (curwin->curpos)	/* backspace */
		   curwin->curpos--;

	      break;

   case '\n': linefeed(curwin);		/* linefeed */
	      break;

   case '\r': break;			/* carriage return */

   case '\t': do			/* horizontal tab */
	      {
		 wputc(' ',outfile);
	      } while (curwin->curpos % 8);

	      break;

   case '\a': Bconout(2,7);		/* bell */
	      if (!auxcon)
		 pop_up();		/* bring under close attention */
	      break;

   case '\0': break;			/* NUL */

   default:   curwin->curline[curwin->curpos++] = c; /* some other char */

	      if (curwin->curpos >= MAXCHAR)	/* right margin? */
		 linefeed(curwin);	/* wrap to next line */
	      else
	      {
		 if (topwin > 0 && win[topwin] == wdw &&
		     curwin->curpos > curwin->hori.pos &&
		     (y = curwin->vert.total -
			  curwin->vert.pos - 1) <= curwin->vert.seen)
		 {
		    vs_clip(handle,TRUE,rc2pts(&curwin->work));

		    x = curwin->work.g_x + 1 +
			(curwin->curpos - 1 - curwin->hori.pos) * gr_hwchar;

		    y = curwin->work.g_y + y * gr_hhchar;

		    outstr[0] = c;
		    outstr[1] = '\0';
		    v_gtext(handle,x,y,outstr);
		 }
		 else
		    curwin->cursor = 2; /* must be updated */
	      }

	      break;
   }

   if (curwin->cursor != 2)
      curwin->cursor = 0;		/* show cursor when needed */

   return (c);
}

/* rq_redraw: request to redraw an area by sending myself a redraw message */

void rq_redraw (wi,area)
   WORD	 wi;				/* window handle */
   register GRECT *area;		/* area to redraw */

{
   WORD msg[8];				/* buffer for message */

   msg[0] = WM_REDRAW;			/* build a REDRAW message */
   msg[1] = gl_apid;			/* coming from myself */
   msg[2] = 0;				/* no more than std. 16 bytes */
   msg[3] = wi;				/* redraw this window */
   msg[4] = area->g_x;			/* area to redraw */
   msg[5] = area->g_y;
   msg[6] = area->g_w;
   msg[7] = area->g_h;

   appl_write(gl_apid,sizeof(msg),msg); /* send myself the message */
}

/*** the one-and-only window redraw routine ***/

/* do_redraw: find and redraw all clipping rectangles in an area */
/* only to be called when a redraw message comes in (use rq_redraw) */

void do_redraw (wi,area)
   WORD	 wi;				/* window handle */
   register GRECT *area;		/* area to redraw */

{
   register WINDOW *curwin = &window[win[wi]];
   GRECT rc;				/* intersection area */
   WORD *pts;
   int	y,skip;
   LINEHIST *lin;
   char m_off = FALSE;			/* mouse already turned off? */

   if (!rc_intersect(&window[0].work,area))/* first intersect with desktop */
      return;				/* nothing to do! (unlikely) */

   wind_update(BEG_UPDATE);		/* lock rectangle list */

   /* get the first rectangle in the 'visible' list for this window */

   wind_get(wi,WF_FIRSTXYWH,&rc.g_x,&rc.g_y,&rc.g_w,&rc.g_h);

   while (rc.g_w && rc.g_h)		/* got a rectangle? */
   {
      if (rc_intersect(area,&rc))	/* is there an intersection? */
      {
	 if (!m_off)			/* mouse already OFF? */
	 {
	    graf_mouse(M_OFF);		/* mouse OFF during updates */
	    m_off = TRUE;		/* now it's off */
	 }

	 pts = rc2pts(&rc);		/* convert inters. area to points */

	 vs_clip(handle,FALSE,pts);	/* clip off for speed */
	 vsf_interior(handle,0);	/* blank the area */
	 vr_recfl(handle,pts);

	 vs_clip(handle,TRUE,pts);	/* clip on */
	 y = curwin->work.g_y;		/* top drawing position */

	 if ((lin = curwin->tail) != NULL) /* lines in history? */
	 {
	    skip = curwin->vert.total - curwin->vert.pos - 1; /* lines to skip back */

	    while (--skip && lin->prev != NULL)
	       lin = lin->prev;		/* work backwards */
	 }

	 while (lin != NULL)		/* quit when all printed */
	 {
	    if (lin->len > curwin->hori.pos)
	       v_gtext(handle,curwin->work.g_x + 1,y,
		       lin->line + curwin->hori.pos);

	    if ((y += gr_hhchar) > rc.g_y + rc.g_h)
	       break;

	    lin = lin->next;
	 }

	 /* if still space, also show current (partial) line */

	 if (y <= rc.g_y + rc.g_h && curwin->curpos > curwin->hori.pos)
	 {
	    curwin->curline[curwin->curpos] = '\0';
	    v_gtext(handle,curwin->work.g_x + 1,y,
		    curwin->curline + curwin->hori.pos);
	 }
      }

      wind_get(wi,WF_NEXTXYWH,&rc.g_x,&rc.g_y,&rc.g_w,&rc.g_h);
   }

   if (m_off)				/* did we turn it OFF? */
      graf_mouse(M_ON);			/* mouse can now be ON again */

   wind_update(END_UPDATE);		/* unlock rectangle list */
   curwin->cursor = 0;			/* should re-display cursor now */
}

/*
 * set the sliders as required.
 *
 * the SLWIN structures 'hori' and 'vert' are used as input.
 * inconsistencies in the position are corrected.
 * the sliders are only set when required.
 *
 */

void sl_update (wi)
   WORD wi;				/* window handle */

{
   register WINDOW *curwin = &window[win[wi]];
   register WORD size,pos;		/* computed size & position */
   register long maxpos;		/* maximal position */
   WORD now,dummy;			/* current value */

   if (curwin->kind & HSLIDE)		/* horizontal slider in use? */
   {
      maxpos = curwin->hori.total - curwin->hori.seen;/* max allowed pos */

      if (curwin->hori.pos > maxpos)	/* bring position within range */
	 curwin->hori.pos = maxpos;

      if (curwin->hori.pos < 0)
	 curwin->hori.pos = 0;

      if (curwin->hori.seen < curwin->hori.total)/* partial view? */
      {
	 pos = (1000L * curwin->hori.pos) / maxpos;
	 size = (1000L * curwin->hori.seen) / curwin->hori.total;
      }
      else				/* we have full-view! */
      {
	 pos = 0;			/* set slider to full size */
	 size = 1000;
      }

      wind_get(wi,WF_HSLIDE,&now,&dummy,&dummy,&dummy);/* get pos */

      if (pos != now)			/* update needed? */
	 wind_set(wi,WF_HSLIDE,pos);	/* then do it */

      wind_get(wi,WF_HSLSIZE,&now,&dummy,&dummy,&dummy);/* get size */

      if (size != now)			/* update needed? */
	 wind_set(wi,WF_HSLSIZE,size);	/* then do it */
   }

   if (curwin->kind & VSLIDE)		/* vertical slider in use? */
   {
      maxpos = curwin->vert.total - curwin->vert.seen;/* max allowed pos */

      if (curwin->vert.pos > maxpos)	/* bring position within range */
	 curwin->vert.pos = maxpos;

      if (curwin->vert.pos < 0)
	 curwin->vert.pos = 0;

      if (curwin->vert.seen < curwin->vert.total)/* partial view? */
      {
	 pos = (1000L * curwin->vert.pos) / maxpos;
	 size = (1000L * curwin->vert.seen) / curwin->vert.total;
      }
      else				/* we have full-view! */
      {
	 pos = 0;			/* set slider to full size */
	 size = 1000;
      }

      wind_get(wi,WF_VSLIDE,&now,&dummy,&dummy,&dummy);/* get pos */

      if (pos != now)			/* update needed? */
	 wind_set(wi,WF_VSLIDE,pos);	/* then do it */

      wind_get(wi,WF_VSLSIZE,&now,&dummy,&dummy,&dummy);/* get size */

      if (size != now)			/* update needed? */
	 wind_set(wi,WF_VSLSIZE,size);	/* then do it */
   }
}

# ifdef GEMPRG
/* handle menu selections */

void mn_select (menu_entry)
   WORD menu_entry;			/* entry index */

{
   GRECT text_rc;			/* menu entry text x, y, w and h */

   switch (menu_entry)			/* the menu entry index */
   {
   case ENABOUT:			/* about this program... */
      ob_rect(rs_trindex[MENUBAR],ENABOUT,&text_rc);/* get menu entry info */
      do_form(rs_trindex[ABOUT],&text_rc);/* display 'ABOUT' dialogue */
      break;

   case ENOPEN:				/* open window */
      pop_up();
      break;

   case ENQUIT:				/* quit program */
      doexit(0);
      break;
   }
}

/*
 * display an input form, await respons,ttnd remove it
 * only the exitbox is deselected on return, others must be deselected
 * by the caller! (after testing their state)
 *
 * the tree address of the object (from rsrc_gaddr) is passed,
 * together with the originating position of the growing box.
 * a NULL org box will suppress the grow/shrink effects.
 * the index of the exit object is returned.
 *
 */

WORD do_form (taddr,org)
   register OBJECT *taddr;		/* form tree address */
   register GRECT  *org;		/* originating pos of growing box */

{
   register WORD exitobj;		/* button used to exit form */
   register char exitform = FALSE;	/* hit real exit button? */
   WORD fo_x,fo_y,fo_w,fo_h;		/* form position */

   form_center(taddr,&fo_x,&fo_y,&fo_w,&fo_h);/* compute position */

					/* reserve screen space */
   form_dial(FMD_START,0,0,0,0,fo_x,fo_y,fo_w,fo_h);

   if (org != NULL)			/* growing boxes wanted? */
      form_dial(FMD_GROW,org->g_x,org->g_y,org->g_w,org->g_h,
			 fo_x,fo_y,fo_w,fo_h);

   objc_draw(taddr,ROOT,MAX_DEPTH,fo_x,fo_y,fo_w,fo_h);/* draw the form */

   while (!exitform)			/* loop until real exit */
   {
      exitobj = form_do(taddr,0) & 0x7fff;/* interact with form */

      switch (taddr[exitobj].ob_type >> 8)/* get 'user type' of exitobj */
      {
      case 0:				/* no user type, real exit */
	 exitform = TRUE;
	 break;

      default:				/* some unknown thing */
	 break;				/* just re-enter */
      }
   }

   if (org != NULL)			/* shrinking box wanted? */
      form_dial(FMD_SHRINK,org->g_x,org->g_y,org->g_w,org->g_h,
			   fo_x,fo_y,fo_w,fo_h);

					/* redraw screen */
   form_dial(FMD_FINISH,0,0,0,0,fo_x,fo_y,fo_w,fo_h);

   taddr[exitobj].ob_state &= ~SELECTED;/* reset exit to 'not selected' */
   return (exitobj);			/* return the exit object# */
}
# endif

/* convert GRECT to an array containing 2 vertices */
/* return a pointer to the result (static area overwritten by each call) */

WORD *rc2pts (grect)
   register GRECT *grect;		/* the input points */

{
   static WORD pts[4];			/* the resulting array */

   pts[0] = grect->g_x;			/* upper left */
   pts[1] = grect->g_y;

   pts[2] = grect->g_x + grect->g_w - 1;/* lower right */
   pts[3] = grect->g_y + grect->g_h - 1;

   return (pts);			/* return a pointer */
}

/* open main window for this application */

void pop_up ()

{
   GRECT rc;				/* work area */

   await |= MU_KEYBD;			/* also attend keyboard events */

   if (window[1].open)			/* #1 already opened? */
   {
      wind_set(topwin = window[1].win,WF_TOP);/* top it */
      return;
   }

   strcpy(window[1].title," KA9Q Internet Protocol Package ");/* set title */
   window[1].info[0] = 0; /* no info */

   window[1].org.g_x = window[0].work.g_x + gr_hwbox;
   window[1].org.g_y = window[0].work.g_y + (gl_mnid + 2) * gr_hhchar;
   window[1].org.g_w = 20 * gr_hwchar;
   window[1].org.g_h = gr_hhchar;

   if (!window[1].work.g_w)	 /* first time? */
   {
      rc.g_w = window[0].work.g_w - gr_hwbox;
      rc.g_h = 10 * gr_hhbox;
      rc.g_x = window[0].work.g_x +
	      (window[0].work.g_w - rc.g_w) / 2;
      rc.g_y = window[0].work.g_y + 3 * gr_hhbox;
      wi_align(&rc);		 /* align on char bound */
   }
   else				 /* use existing size */
   {
      wind_calc(0,WI_KIND,window[1].work.g_x,
			  window[1].work.g_y,
			  window[1].work.g_w,
			  window[1].work.g_h,
			 &rc.g_x,&rc.g_y,&rc.g_w,&rc.g_h);
   }

   wi_open(1,WI_KIND,&rc);
}
#endif	/* GEM */

/* wait for interrupt (?) and return */
/* in normal version: check for stack overflows, spend time in system */
/* in desk-accessory version: await next command or timer event */

void eihalt ()

{
#ifdef GEM
   WORD button;				/* current button state */
   WORD bclicked;			/* mouse button clicks */
   WORD mx,my;				/* mouse position */
   WORD keycode;			/* code of pressed key */
   WORD keystat;			/* Shift/Ctrl/Alt state */
   WORD msgbuff[8];			/* incoming message buffer */
   WORD d;				/* dummy word */
   long count;				/* timer millisecond count */
   GRECT rc;				/* scratch */
   register WORD event;			/* event that occurred */
   register WINDOW *curwin;		/* scratch window pointer */
#endif

#ifndef GEMACC
   if (watchhw)
      Giaccess(watchhw,0x8f);		/* put reset on the printerport */

   if (STACK[0] != STKMAGIC ||		/* check the magic numbers */
       STACK[16] != STKMAGIC ||
       STACK[32] != STKMAGIC)
   {
      printf("Stack overflow!\n");
      while (kbread() == -1)
	 ;
      doexit(0);
   }

   watchcnt = watchnet;			/* reset the watchdog */
   if (watchhw)
      Giaccess(0,0x8f);			/* put 00 on the printerport */
#endif

#ifdef GEM
   for (;;)
   {
      if (keyqueue)			/* still some key pending? */
	 return;			/* then process it first */

      if (!fastpoll)			/* determine polling rate */
	 count = 1000;			/* slow rate 1 second */
      else
      {
	 count = 100;			/* fast rate .1 second */

	 if (!--fastpoll)		/* check if next still fast */
	 {
	    if (window[1].cursor == 2)	/* unredrawn info in #1? */
	       rq_redraw(window[1].win,&window[1].work);/* draw desired window */
	 }
      }

      event = evnt_multi(await,		/* events we await */
		 2,1,1,			/* BUTTON (left only) */
		 m1flag,m1.g_x,m1.g_y,	/* M1 */
			m1.g_w,m1.g_h,
		 m2flag,m2.g_x,m2.g_y,	/* M2 */
			m2.g_w,m2.g_h,
		 msgbuff,		/* MESAG result */
		 (WORD) count,(WORD) (count >> 16),/* TIMER */
		 &mx,&my,		/* BUTTON, M1, M2 result */
		 &button,&keystat,	/* BUTTON, M1, M2 result */
		 &keycode,		/* KEYBD result */
		 &bclicked);		/* BUTTON result */

      wind_get(DESK,WF_TOP,&topwin,&d,&d,&d);/* get current top window */

      if (event & MU_KEYBD)		/* keyboard input */
      {
	 keyqueue = keycode;		/* save it */
	 keyqstat = keystat;
	 fastpoll = 50;			/* increase activity */
      }

      if (event & MU_MESAG)		/* incoming message? */
      {
	 switch (msgbuff[0])		/* check message type */
	 {
# ifdef GEMPRG
	 case MN_SELECTED:		/* selected a menu item */
	    mn_select(msgbuff[4]);	/* process selection */
	    menu_tnormal(rs_trindex[MENUBAR],msgbuff[3],TRUE);/* title back to normal */
	    break;
# endif

	 case WM_REDRAW:		/* window redraw request */
	    do_redraw(msgbuff[3],(GRECT *) &msgbuff[4]);
	    break;

	 case WM_TOPPED:		/* bring it to the top */
	    curwin = &window[win[msgbuff[3]]]; /* point to window info */
	    if (curwin->cursor == 2 &&	 /* unredrawn info in it? */
		curwin->vert.pos >= curwin->vert.total - curwin->vert.seen)
	       rq_redraw(curwin->win,&curwin->work);/* draw desired window */

	    wind_set(topwin = msgbuff[3],WF_TOP);
	    break;

	 case WM_CLOSED:		/* window closed */
	    wi_close(msgbuff[3]);	/* just close the window */
	    break;

	 case WM_FULLED:		/* wants to set to full screen */
	    curwin = &window[win[msgbuff[3]]]; /* point to window info */

	    wind_get(msgbuff[3],WF_CURRXYWH,&rc.g_x,&rc.g_y,&rc.g_w,&rc.g_h);

	    if (rc_equal(&rc,&curwin->full))/* already fulled? */
	    {
		wind_get(msgbuff[3],WF_PREVXYWH,&rc.g_x,&rc.g_y,&rc.g_w,&rc.g_h);

		graf_shrinkbox(rc.g_x,rc.g_y,rc.g_w,rc.g_h,
			       curwin->full.g_x,curwin->full.g_y,
			       curwin->full.g_w,curwin->full.g_h);

		wind_set(msgbuff[3],WF_CURRXYWH,rc.g_x,rc.g_y,rc.g_w,rc.g_h);
	    }
	    else
	    {
		graf_growbox(rc.g_x,rc.g_y,rc.g_w,rc.g_h,
			     curwin->full.g_x,curwin->full.g_y,
			     curwin->full.g_w,curwin->full.g_h);

		wind_set(msgbuff[3],WF_CURRXYWH,curwin->full.g_x,curwin->full.g_y,
						curwin->full.g_w,curwin->full.g_h);
	    }

	    wi_getwork(msgbuff[3]);	/* get new size */
	    sl_update(msgbuff[3]);	/* update sliders */
	    break;

	 case WM_ARROWED:		/* clicked arrows/scrollbar */
	    curwin = &window[win[msgbuff[3]]];/* point to window info */

	    switch (msgbuff[4])		/* get more detail */
	    {
	    case 0:			/* page up */
		curwin->vert.pos -= curwin->vert.seen;
		break;

	    case 1:			/* page down */
		curwin->vert.pos += curwin->vert.seen;
		break;

	    case 2:			/* row up */
		curwin->vert.pos--;
		break;

	    case 3:			/* row down */
		curwin->vert.pos++;
		break;

	    case 4:			/* page left */
		curwin->hori.pos -= curwin->hori.seen;
		break;

	    case 5:			/* page right */
		curwin->hori.pos += curwin->hori.seen;
		break;

	    case 6:			/* column left */
		curwin->hori.pos--;
		break;

	    case 7:			/* column right */
		curwin->hori.pos++;
		break;
	    }

	    sl_update(msgbuff[3]);	/* update sliders */
	    rq_redraw(msgbuff[3],&curwin->work);/* draw desired window */
	    break;

	 case WM_HSLID:			/* moved horizontal slider */
	    curwin = &window[win[msgbuff[3]]];/* point to window info */
	    curwin->hori.pos = (long) msgbuff[4] *
				  (curwin->hori.total - curwin->hori.seen) / 1000L;
	    sl_update(msgbuff[3]);	/* update sliders */
	    rq_redraw(msgbuff[3],&curwin->work);/* draw desired window */
	    break;

	 case WM_VSLID:			/* moved vertical slider */
	    curwin = &window[win[msgbuff[3]]];/* point to window info */
	    curwin->vert.pos = (long) msgbuff[4] *
				  (curwin->vert.total - curwin->vert.seen) / 1000L;
	    sl_update(msgbuff[3]);	/* update sliders */
	    rq_redraw(msgbuff[3],&curwin->work);/* draw desired window */
	    break;

	 case WM_SIZED:			/* changed window size */
	    if (msgbuff[6] < MIN_WIDTH) /* too small? */
		msgbuff[6] = MIN_WIDTH; /* set to minimal value */

	    if (msgbuff[7] < MIN_HEIGHT)
		msgbuff[7] = MIN_HEIGHT;

	    wi_align(&msgbuff[4]);	/* align on char bound */
	    wind_set(msgbuff[3],WF_CURRXYWH,msgbuff[4],msgbuff[5],
					    msgbuff[6],msgbuff[7]);

	    wi_getwork(msgbuff[3]);	/* update pos/size */
	    sl_update(msgbuff[3]);	/* update sliders */
	    break;

	 case WM_MOVED:			/* moved to another pos */
	    wi_align(&msgbuff[4]);	/* align on char bound */
	    wind_set(msgbuff[3],WF_CURRXYWH,msgbuff[4],msgbuff[5],
					    msgbuff[6],msgbuff[7]);

	    wi_getwork(msgbuff[3]);	/* update pos/size */
	    break;

	 case WM_NEWTOP:		/* placed on top */
	    curwin = &window[win[msgbuff[3]]]; /* point to window info */
	    if (curwin->cursor == 2 &&	 /* unredrawn info in it? */
		curwin->vert.pos >= curwin->vert.total - curwin->vert.seen)
	       rq_redraw(curwin->win,&curwin->work);/* draw desired window */

	    topwin = msgbuff[3];	/* keep top window handle */
	    break;

# ifdef GEMACC
	 case AC_OPEN:			/* selected accessory */
	    if (msgbuff[4] == gl_mnid)	/* doc says it's in word 3! */
	       pop_up();		/* pop-up our main window */
	    break;

	 case AC_CLOSE:			/* removed accessory */
	    if (msgbuff[3] == gl_mnid)	/* make sure it's for me! */
	    {
	       for (d = 1; d < USEWIN; d++)/* yep, so all user windows */
		  window[d].open = FALSE;/* have been closed and deleted */

	       for (d = 1; d < MAXWIN; d++)/* also get rid of handle info */
		  win[d] = 0;

	       topwin = -1;		/* WE are not on top anymore! */
	       await &= ~MU_KEYBD;	/* keyboard not of interest */
	    }
	    break;
# endif
	 }

	 fastpoll = 50;			/* message came in, be active */
      }

      if (event & MU_TIMER)		/* time expired */
	 return;			/* continue in KA9Q code */
   }
#else					/* not GEM */
   /* spend some time in system, mainly for profiling */
   Vsync();
   Vsync();
#endif
}

#ifdef GEMACC
/* desk accessories must never exit... */

exit (val)
   int val;

{
   printf("\nAttempt to exit(%d)\n",val);

   for (;;)
   {
      keyqueue = 0;			/* force action */
      eihalt();
   }
}

# ifdef MWC
/* the allocation routines are not designed for desktop accessory use... */
/* define a replacement handler for low-level allocation */

extern int end[];			/* end of program */
static long Memtop = end;		/* current end of alloced space */
static char Mallocspace[50000L];	/* fixed area for dynamic allocation */
static long Mallocused = 0;		/* amount of area actually used now */

long lsbrk (incr)
	register long incr;		/* storage increment */

{
	register long rv;		/* return value */

	if (incr < 0)			/* decreasing top? */
		return (-1L);		/* not supported... */

	if (incr == 0)			/* query? */
		return (Memtop);	/* return current top */

	incr = (incr + 1) & 0xfffffffeL; /* incr must be even */
	rv = (long) (Mallocspace + Mallocused);/* compute address of new space */

	if ((rv + incr) > (long) (Mallocspace + sizeof(Mallocspace)))
		return (-1L);		/* return -1 on failure */

	Mallocused += incr;		/* reserve the space */
	Memtop = rv + incr;		/* set new top */

	return (rv);			/* and return the pointer */
}
# endif /* MWC */
#endif	/* GEM */

/* return the amount of free memory (= available from TOS) */

static long freemem ()

{
#ifdef GEMACC
   return ((long) sizeof(Mallocspace) - Mallocused);
#else
   long size;
   char *addr;

   if ((size = (long) Malloc(-1L)) < 256L) /* get largest available block */
      return (size);			/* when <= 256, stop looking */

   addr = (char *) Malloc(size);	/* acquire the specified block */
   size += freemem();			/* add this + any other blocks */
   Mfree(addr);				/* return the allocated block */
   return (size);			/* return total size */
#endif
}


/* Print free list map, when arg given also show free block sizes */
/* when numeric arg given, alloc/free that number of bytes */

int
memstat (argc,argv)
	int argc;
	char *argv[];

{
#ifdef MWC
/* This is severely dependent on the inner workings of the Mark Williams */
/* allocation routines...... */

	struct memblock {
		long size;
		struct memblock *next;
	};
	extern struct memblock *_a_scanp;
	register struct memblock *mp,*nmp;
	register long bsize;
	register int  bmode;
	int nblocks[2];
	long nbytes[2];
	long mxsize[2];
	int nchunks;
	char *lmalloc();

	if (argc > 1 && isdigit(argv[1][0])) {
	    if ((mp = (struct memblock *) lmalloc(atol(argv[1]))) == 0) {
		printf("malloc failure\n");
		return 1;
	    }

	    free((char *) mp);
	    return 0;
	}

	nblocks[0] = nblocks[1] = nchunks = 0;
	nbytes[0] = nbytes[1] = mxsize[0] = mxsize[1] = 0;
	if (argc > 1)
	    printf("free block sizes:\n");

	mp = _a_scanp;				/* get start of blocklist */
	do {
	    if (mp->size == 0){			/* zero size is a chain item */
		nchunks++;			/* count them */
		mp = mp->next;
	    } else {
		if ((bsize = mp->size & ~1L) < 0)/* blocksize, always even */
		    break;			/* quit when <0 (panic) */

		if (bmode = mp->size & 1)	/* 0=alloced, 1=free */
		    /* when free, check if next also free */
		    while ((nmp = (struct memblock *) ((char *) mp + bsize)) != _a_scanp &&
			   (nmp->size & 1))
			/* join next (free) block with this one */
			mp->size = (bsize += nmp->size & ~1L) | 1;

		nblocks[bmode]++;
		nbytes[bmode] += bsize;
		if (bsize > mxsize[bmode])
			mxsize[bmode] = bsize;
		if (bmode && argc > 1)
			printf("%8ld",bsize - sizeof(long));

		((char *) mp) += bsize;		/* advance ptr to next */
	    }
	} while (mp != _a_scanp && nchunks < 1000); /* continue until complete round */

	if (argc > 1)
	    printf("\n");

	if (bsize < 0)
	    printf("panic: size of at least one block <0!! (%ld @ %06x)\n",
		   mp->size,&mp->next);

	printf("%7ld bytes claimed in %d chunks\n",nbytes[0] + nbytes[1],nchunks);
	printf("%7ld bytes available from system\n",freemem());
	for (bmode = 0; bmode < 2; bmode++)
		printf("%7ld bytes %s in %3d blocks (largest %ld)\n",
			nbytes[bmode],(bmode? "free" : "used"),
			nblocks[bmode],mxsize[bmode] - sizeof(long));
#endif
	return 0;
}

#ifndef GEMACC
/* called at each timer tick.  decement a counter and RESET if it */
/* reaches zero.  the processor is already in supervisor mode. */

static void watchtick (timer_ms)
unsigned int timer_ms;

{
	if (watchcnt != 0 && --watchcnt == 0){
	    *((long *) 0x3fc) = 0x53574154L; /* set signature (see log proc) */
	    (**((int (**)()) 4L))();	/* jump via RESET vector */
	}

	(*orgtick)(timer_ms);		/* call original handler */
}
#endif

/* reboot the system.  this is used when the SYS_RESET function code */
/* is used on the "remote" server */

sysreset()

{
	Super(NULL);			/* switch to supervisor mode */
	*((long *) 0x3fc) = 0x0L;	/* remove signature (see log proc) */
	(**((int (**)()) 4L))();	/* jump via RESET vector */
}

#ifdef MWC
/* Select screen output mode */
int
doscreen (argc,argv)
int argc;
char *argv[];

{
    int conputc();			/* console putc routine */

    fflush(stdout);			/* first flush any pending output */

    switch (argv[1][0])			/* select on specified mode */
    {
# ifndef GEM
    case 'b':				/* using BIOS output */
	screenmode = SCRBIOS;
	stdout->_pt = conputc;
	break;
# endif

    case 'd':				/* using direct out to video */
	screenmode = SCRDIR;
# ifndef GEM
	linea0();			/* get line A info */
	logbase = Logbase();		/* read logical screen base */
	stdout->_pt = conputc;
# endif
	break;

    case 't':				/* using output via TOS */
	screenmode = SCRTOS;
# ifndef GEM
	stdout->_pt = _fputt;
# endif
	break;

    default:				/* unrecognized */
	return -1;			/* cmdparse will print options */
    }

    return 0;
}

/* utility routines for output redirection */

# ifndef GEM
/* put byte on AUX and/or console */
int conputc(c,outfile)
    int c;
    FILE *outfile;

{
    register char *set,*screen;

    if (outfile == stdout)
    {
	if (auxcon & AUXCOUT)		/* output to AUX? */
	{
	    if (c == '\n')
		Bconout(1,auxotrn['\r']);

	    Bconout(1,auxotrn[uchar(c)]);
	}

	if (auxcon & AUXCNOT)		/* no output to screen? */
	    return c;

	if (cursor)			/* cursor still on? */
	{
	    cursor = 0;
	    Bconout(2,'\033');		/* switch it off */
	    Bconout(2,'f');
	}

	switch (screenmode)
	{
	case SCRBIOS:			/* output via BIOS */
	    if (c == '\n')
		Bconout(2,'\r');

	    Bconout(2,c);
	    return c;

	case SCRDIR:
	    switch (c)			/* interpret char */
	    {
	    case '\n':			/* new line */
		Bconout(2,'\r');	/* also does a CR */
		if (V_CUR_CY == V_CEL_MY) /* check for last screen line */
		{
		    while (Kbshift(-1) & 0x08) /* Alternate key pressed? */
			Vsync();	/* then hold screen */

		    do_scroll(logbase); /* move it up by one line */
		    V_CUR_CY--;		/* and tell that to line A */
		}

	    case '\a':			/* bell */
	    case '\b':			/* backspace */
	    case '\t':			/* tab */
	    case '\r':			/* carriage return */
		Bconout(2,c);
		break;

	    case '\0':			/* NUL */
		break;

	    default:
		set = (char *) V_FNT_AD + uchar(c);
		screen = (char *) V_CUR_AD; /* cursor address */

		switch (VPLANES)	/* depending on video mode... */
		{
		case 1:
		    screen[ 0 * 80] = set[ 0 * 256]; /* blit the single character */
		    screen[ 1 * 80] = set[ 1 * 256];
		    screen[ 2 * 80] = set[ 2 * 256];
		    screen[ 3 * 80] = set[ 3 * 256];
		    screen[ 4 * 80] = set[ 4 * 256];
		    screen[ 5 * 80] = set[ 5 * 256];
		    screen[ 6 * 80] = set[ 6 * 256];
		    screen[ 7 * 80] = set[ 7 * 256];
		    screen[ 8 * 80] = set[ 8 * 256];
		    screen[ 9 * 80] = set[ 9 * 256];
		    screen[10 * 80] = set[10 * 256];
		    screen[11 * 80] = set[11 * 256];
		    screen[12 * 80] = set[12 * 256];
		    screen[13 * 80] = set[13 * 256];
		    screen[14 * 80] = set[14 * 256];
		    screen[15 * 80] = set[15 * 256];

		    if (V_CUR_CX < V_CEL_MX)
		    {
			V_CUR_AD++;	/* simply increment addr */
			V_CUR_CX++;	/* and cursor position */
		    }
		    else
		    {
			Bconout(2,'\r'); /* wrap cursor */
			if (V_CUR_CY == V_CEL_MY)
			{
			    do_scroll(logbase);
			    V_CUR_CY--;
			}
			Bconout(2,'\n');
		    }
		    break;

		case 2:
		    screen[0 * 160] = screen[0 * 160 + 2] = set[0 * 256];
		    screen[1 * 160] = screen[1 * 160 + 2] = set[1 * 256];
		    screen[2 * 160] = screen[2 * 160 + 2] = set[2 * 256];
		    screen[3 * 160] = screen[3 * 160 + 2] = set[3 * 256];
		    screen[4 * 160] = screen[4 * 160 + 2] = set[4 * 256];
		    screen[5 * 160] = screen[5 * 160 + 2] = set[5 * 256];
		    screen[6 * 160] = screen[6 * 160 + 2] = set[6 * 256];
		    screen[7 * 160] = screen[7 * 160 + 2] = set[7 * 256];

		    if (V_CUR_CX < V_CEL_MX)
		    {
			V_CUR_AD += (V_CUR_CX & 1)? 3 : 1;
			V_CUR_CX++;
		    }
		    else
		    {
			Bconout(2,'\r'); /* wrap cursor */
			if (V_CUR_CY == V_CEL_MY)
			{
			    do_scroll(logbase);
			    V_CUR_CY--;
			}
			Bconout(2,'\n');
		    }
		    break;
		}
		break;
	    }
	    return c;
	}
    }

    return (_fputt(c,outfile));		/* call original out char routine */
}
# endif

/* route standard I/O to the AUX port, to facilitate control via a */
/* terminal connected to this port */
/* Usage: auxcon <io code> [<baudrate> [7|8 [noflow]]] */
/* <io code> is 10 (input) | 01 (output) | 02 (no screen output) */
int
doauxcon(argc,argv)
int argc;
char *argv[];
{
   register int i;
   int sp = -1;				/* default = unchanged speed */
   int ucr = 0x88;			/* 8bit, no parity, 1 stopbit */
   int flow = 1;			/* default = XON/XOFF flow ctrl */

# ifndef GEM
   stdout->_pt = (screenmode == SCRTOS)? _fputt : conputc;
# endif

   if ((auxcon = htoi(argv[1])) == 0)	/* convert arg, 0 = off */
      return 0;

   for (i = 0; i < nasy; i++)
      if (asy[i].addr == RS232)
      {
	 auxcon = 0;
	 printf("AUX: already in use for SLIP or KISS\n");
	 return 1;
      }

   for (i = 0; i < 256; i++)		/* init translate tables */
      auxotrn[i] = auxitrn[i] = i;

   auxotrn[0x11] = auxotrn[0x13] = '.'; /* inhibit XON and XOFF */

   /* set some keycodes. these values are especially good for the */
   /* EPSON PX-8. this should be made variable for other terminals. */

   auxitrn[0x03] = -2;			/* STOP = command mode (F10) */
   auxitrn[0x0b] = 0x12;		/* HOME = retype line (^R) */
   auxitrn[0x12] = -12;			/* INS	= insert mode */
   auxitrn[0x1c] = -16;			/* RIGHT */
   auxitrn[0x1d] = -17;			/* LEFT */
   auxitrn[0x1e] = -19;			/* UP */
   auxitrn[0x1f] = -14;			/* DOWN */

   if (argc > 2)
      switch (atoi(argv[2]))
      {
      case 19200:  sp = 0; break;
      case  9600:  sp = 1; break;
      case  4800:  sp = 2; break;
      case  3600:  sp = 3; break;
      case  2400:  sp = 4; break;
      case  1200:  sp = 7; break;
      case   600:  sp = 8; break;
      case   300:  sp = 9; break;
      default:
	 printf("auxcon: unsupported speed %s (speed unchanged)\n",argv[2]);
	 break;
      }

   if (argc > 3 && argv[3][0] == '7')
   {
      ucr = 0x0ae;		/* 7 bit, even parity, 1 stopbit */

      for (i = 128; i < 256; i++)
      {
	 auxotrn[i] = '.';
	 auxitrn[i] = auxitrn[i - 128];
      }
   }

   if (argc > 4 && argv[4][0] == 'n')
      flow = 0;			/* no flow control */

   Rsconf(sp,flow,ucr,0x01,0x01,0x00); /* set the MFP serial channel */
# ifndef GEM
   if (auxcon & AUXCOUT)	/* output to AUX selected? */
      stdout->_pt = conputc;	/* set replacement "put char" routine */
# endif
   return 0;
}
#endif


/* Print or Set the date and time */

dodate(argc,argv)
	int argc;
	char *argv[];
{
	long tloc;
	register char *p;
	int y,m,d,h,n,s;
	extern long starttime;
#ifdef MWC
	extern long _boottime;
#endif

	if (argc < 2) {
	    time(&tloc);
	    printf("%s",ctime(&tloc));
	    return 0;
	}


	if (*(p = argv[1]) == 's') {	/* starttime */
	    printf("starttime: %s",ctime(&starttime));
	    return 0;
	}

#ifdef MWC
	if (*p == 'b') {		/* boottime */
	    time(&tloc);
	    printf("boottime: %s",ctime(&_boottime));
	    return 0;
	}
#endif

	if (strlen(p) != 10 && strlen(p) != 12)
	    return -1;
	while (*p)
	    if (!isdigit(*p++))
		return -1;

	p = argv[1];
	m = 10 * p[0] + p[1] - 11 * '0';
	d = 10 * p[2] + p[3] - 11 * '0';
	h = 10 * p[4] + p[5] - 11 * '0';
	n = 10 * p[6] + p[7] - 11 * '0';
	s = 10 * p[8] + p[9] - 11 * '0';
	if (p[10]) {
	    if ((y = 10 * p[10] + p[11] - 11 * '0' + 1900) < 1980)
		y += 100;
	} else
	    y = ((Tgetdate() >> 9) & 0x7f) + 1980;

	if (Tsettime((h << 11) | (n << 5) | (s >> 1)) ||
	    Tsetdate(((y - 1980) << 9) | (m << 5) | d))
	    return -1;

	Settime(((long) (y - 1980) << 25) | ((long) m << 21) | ((long) d << 16) |
		((long) h << 11) | (n << 5) | (s >> 1));

#ifdef MWC
	_boottime = 0;			/* reset MW's idea of time */
#endif
	return 0;
}

/* Spawn subshell */
/* extension: "shell =name" sets the program to run as a shell */

doshell(argc,argv)
	int argc;
	char *argv[];
{
	static char command[80] = "COMMAND.PRG";
#ifndef GEM
	char tail[128], *p = tail + 1,**envp;
	int  arg,len;
	long ret;
	int  curdrive;
	char curdir[66];
#endif

	if (argc == 2 && argv[1][0] == '=')
	{
		strcpy(command,argv[1] + 1);
		return 0;
	}

#ifdef GEM
	printf("No shell in GEM version...\n");
#else
	curdrive = Dgetdrv();		/* get current drive (0,1,...) */

	if (Dgetpath(tail,0) == 0){	/* get current dir */
		sprintf(curdir,"%c:%s%s",(char) curdrive+'A',
			(*tail? "" : "\\"),tail);
	}

	tail[0] = 0;

	for (arg = 1; arg < argc; arg++)
	{
		len = strlen(argv[arg]);

		if ((len + tail[0]) < 127)
		{
			if (tail[0])
			{
				*p++ = ' ';
				tail[0]++;
			}

			strcpy(p,argv[arg]);
			p += len;
			tail[0] += len;
		}
		else
			break;
	}

# ifdef SERVERS
	if (logfp != NULLFILE)		/* logging to file? */
		fclose(logfp);
# endif

# ifdef TRACE
	if (trfp != stdout)		/* trace to file? */
		fclose(trfp);		/* close it during shell exec */
# endif

	Bconout(2,'\033');		/* make sure cursor is ON */
	Bconout(2,'e');
	cursor = 1;

	watchcnt = watchsh;		/* set SHELL watchdog time */

	if ((envp = make_env()) != NULLCHRP)
	    ret = Pexec(0,command,tail,envp[0]);
	else
	    ret = Pexec(0,command,tail,NULLCHRP);

	free_env();

	Dsetdrv(curdrive);		/* reset current directory */
	Dsetpath(curdir);

# ifdef SERVERS
	if (logfp != NULLFILE)		/* re-open logfile if it was open */
		logfp = fopen(logname,"a+");
# endif

# ifdef TRACE
	if (trfp != stdout)		/* re-open tracefile if not stdout */
		if ((trfp = fopen(trname,"a+")) == NULLFILE)
			trfp = stdout;
# endif

	Bconout(2,'\033');		/* make sure cursor is ON */
	Bconout(2,'e');
	Bconout(2,'\033');		/* autoWRAP ON */
	Bconout(2,'v');

	if (ret < 0)
	{
		printf("%s: cannot execute\n",command);
		return -1;
	}
#endif

	return 0;
}

/* control the watchdog function */

dowatchdog (argc,argv)
	int argc;
	char *argv[];
{
#ifndef GEMACC
	unsigned int timer_ms = Tickcal();

	switch (argc)
	{
	case 4:
		watchhw = atoi(argv[3]) & 0xff;
	case 3:
		watchsh = (atol(argv[2]) * 1000) / timer_ms;
	case 2:
		watchnet = (atol(argv[1]) * 1000) / timer_ms;
		break;
	case 1:
		printf("NET=%ld SHELL=%ld HW=%u\n",
			(watchnet * timer_ms) / 1000,
			(watchsh * timer_ms) / 1000,watchhw);
		break;
	}
#endif
	return 0;
}

/* interrupt latency measurement (for testing and optimizing purposes) */

#define MODE		4
#define PRESCALE	50
#define CLOCK		2457600L

unsigned long latency[256];
static int lat_active = 0;

static void lat_stop ()

{
    Xbtimer(0,0,0,NULLVFP);		/* stop timer & remove handler */
}

static void lat_start ()

{
    extern void lathandler();

    Xbtimer(0,MODE,255,lathandler);	/* init timer and start it */
}

/* start/stop/display latency measurement */

dolatency (argc,argv)
	int argc;
	char *argv[];
{
    switch (tolower(argv[1][0]))
    {
    case 'n':				/* stop measurement */
	lat_stop();
	lat_active = 0;
	break;

    case 'y':				/* clear & start measurement */
	lat_stop();			/* first stop it for safety */
	memset(latency,0,sizeof(latency)); /* clear the tallies */
	lat_start();			/* start measurement */
	lat_active = 1;
	break;

    case 'r':				/* show raw data */
	lat_raw();
	break;

    case 's':				/* show status (results) */
	if (!lat_active) {
	    printf("latency measurement not active\n");
	    break;
	}
	lat_stop();
	lat_status();
	lat_start();
	break;

    default:
	return -1;
    }

    return 0;
}

static char *pltime (timer)
    unsigned int timer;

{
    static char buf[80];

    timer = 255 - timer;		/* compute elapsed time */

    sprintf(buf,"%6lu us",(1000000L*PRESCALE*timer)/CLOCK);

    if (timer != 0)
	sprintf(buf + 9,", %6lu/s, %6lu bps",CLOCK/((long) PRESCALE*timer),
					 (CLOCK*8)/((long) PRESCALE*timer));

    return buf;
}

static void lat_raw ()

{
    unsigned int i = 255;

    do
    {
	if (latency[i] != 0)
	    printf("%9lu * %s\n",latency[i],pltime(i));
    }
    while (i-- != 0);
}

static void lat_status ()

{
    unsigned int i,j;
    unsigned int lng,max;
    unsigned long maxv,totl,ttl,lim;

    for (lng = 0; lng < 256; lng++)	/* find longest latency */
	if (latency[lng] != 0)
	    break;

    maxv = totl = 0;

    for (i = lng; i < 256; i++)
    {
	totl += latency[i];		/* count total number of ticks */

	if (latency[i] > maxv)		/* record largest number */
	{
	    maxv = latency[i];		/* found a larger number! */
	    max = i;
	}
    }

    printf("%lu samples\n\n",totl);	/* print total number of samples */

    printf("normal:  %s (%lu%%)\n",pltime(max),maxv / (totl / 100L));
    printf("longest: %s (%lu)\n\n",pltime(lng),latency[lng]);

    lim = 1;

    for (j = 1; j < 10; j++)		/* 10^1 .. 10^9 */
    {
	lim *= 10;

	if ((maxv = (totl / lim)) == 0) /* total #ticks / 10^n */
	    break;

	ttl = 0;

	for (i = 0; i < 256; i++)
	    if ((ttl += latency[i]) > maxv)
		break;

	printf("<1 in 10^%d longer than %s\n",j,pltime(i));
    }
}

#if (defined(MWC) && (MWC < 306))
/* Mark Williams C has a bug that influences '>> 16' constructs... */
/* when you shift a long value by exactly 16 bits, the compiler */
/* cleverly codes a "swap" and "ext.w", it should have been "ext.l" */
/* we define this variable to generate less-optimized code (see global.h) */
/* The bug was fixed in 3.0.6 (according to Howard Chu) */

int Sixteen = 16;			/* shift for hiword... */
#endif

