/*
 *			D M - P S . C
 *
 *  MGED display manager for the Evans & Southerland Picture System 300
 *  display system.  This version only works over the parallel UNIBUS
 *  interface, and interfaces to the E&S provided Graphics Service Routine
 *  (GSR) library.
 *
 ****  BANDAID VERSION *****
 *	The illuminated solid is often not defined so by the state of
 *	s_iflag.  We force it to be illuminated in Ps_statechange().
 *
 *  Authors -
 *	Michael John Muuss
 *	Terry Slattery
 *	Steve Saterfield
 *  
 *  Source -
 *	SECAD/VLD Computing Consortium, Bldg 394
 *	The U. S. Army Ballistic Research Laboratory
 *	Aberdeen Proving Ground, Maryland  21005
 *  
 *  Copyright Notice -
 *	This software is Copyright (C) 1985 by the United States Army.
 *	All rights reserved.
 */
#ifndef lint
static char RCSid[] = "@(#)$Header: dm-ps.c,v 2.7 86/03/14 20:43:29 mike Exp $ (BRL)";
#endif

#include <fcntl.h>
#include <stdio.h>
#include <netdb.h>
#include "./machine.h" /* special copy */
#include "../h/vmath.h"
#include "ged.h"
#include "dm.h"
#include "solid.h"
#include <ps300/gsrext.h>
#include <ps300/netdefs.h>
#include <ps300/pslib.h>

typedef unsigned char u_char;

/* Display Manager package interface */

#define PSBOUND	4095.9	/* Max magnification in Rot matrix */
int	Ps_open();
void	Ps_close();
int	Ps_input();
void	Ps_prolog(), Ps_epilog();
void	Ps_normal(), Ps_newrot();
void	Ps_update();
void	Ps_puts(), Ps_2d_line(), Ps_light();
int	Ps_object();
unsigned Ps_cvtvecs(), Ps_load();
void	Ps_statechange(), Ps_viewchange(), Ps_colorchange();
void	Ps_window(), Ps_debug();

struct dm dm_Ps = {
	Ps_open, Ps_close,
	Ps_input,
	Ps_prolog, Ps_epilog,
	Ps_normal, Ps_newrot,
	Ps_update,
	Ps_puts, Ps_2d_line,
	Ps_light,
	Ps_object,
	Ps_cvtvecs, Ps_load,
	Ps_statechange,
	Ps_viewchange,
	Ps_colorchange,
	Ps_window,
	Ps_debug,
	0,			/* no "displaylist", per. se. */
	PSBOUND,
	"ps", "PS 300"
};


struct timeval	{			/* needed for select() */
	long	tv_sec;			/* seconds */
	long	tv_usec;		/* microseconds */
};
#define DEV_DISPLAYLIST "_usna-ps2 or _usna-ps5"

extern int xt_chan;			/* E&S PS300 library file descr */
static int	psfd;			/* raw DMA device file descr. */

extern struct device_values dm_values;	/* values read from devices */

/**** Begin global display information, used by dm.c *******/
extern int	inten_offset;		/* Intensity offset */
extern int	inten_scale;		/* Intensity scale */
extern int	xcross;
extern int	ycross;			/* tracking cross position */
extern mat_t	rot;			/* viewing rotation */
/**** End global display information ******/
static int	ps_window[6];	/* X hi,lo;  Y hi,lo;  Z hi,lo */

/**** Global mode information ****/
static int	ps_debug;		/* toggled by "X" command */
extern int	adcflag;		/* A/D cursor on/off */
/**** End Global mode information ****/

/* Mapping from PS300 buttons to GED button functions.
 * map 0 is for viewing, and map 1 is for editing functions.
 * [0] = F1, [11] = F12.
 */
static unsigned char bviewmap[12] = {
	BV_TOP,		BV_RIGHT,	BV_FRONT,	BV_35_25,
	BV_BOTTOM,	BV_LEFT,	BV_REAR,	BV_90_90,
	BV_RESET,	BV_SLICEMODE,	BV_ADCURSOR,	BV_SHIFT
};
static unsigned char beditmap[12] = {
	BE_S_ILLUMINATE,BE_O_ILLUMINATE,BE_O_X,		BE_O_Y,
	BE_O_XY,	BE_O_SCALE,	BE_O_ROTATE,	BE_S_ROTATE,
	BE_S_TRANS,	BE_REJECT,	BE_ACCEPT,	BE_SHIFT
};
static int bviewflag = 1;
static unsigned char PickPath;
#define SHIFT_BUTTON	11	/* button # to xch view & edit modes */

/* Maps BE_ and BV_ functions into {mapno, button} pairs */
static struct fun_map  {
	unsigned char fm_viewmap;	/* Which button map function is in */
	unsigned char fm_button;/* Which button function is on */
 	unsigned char fm_blink; /* blinking button flag, 1=blink */
} invmap[BV_MAXFUNC+1];

static double charscale = 1.0;

/* static */ void	pscheck();

/*
 *			P S _ O P E N
 *
 * Fire up the display manager, and the display processor.
 *
 */
Ps_open()
{
	register int i, j;
	register int count;
	char *fullpath;
	char device[64], device2[64];
	char Dname[20];
	char Dname1[20];
	char Cname[20];
	char Dacc[20];
	struct hostent *addr;
	char *PDevName();
	char *getenv();

	/* Protect ourselves. */
	sync();

	fullpath = getenv("TERM");
	if(strncmp(fullpath, "ps", 2) == 0){
		fullpath = PDevName();
		if(fullpath[0] != '_'){
			fprintf(stderr,"Mged: must use PS2 or PS5\n" );
			exit(-1);
		}
		strcpy(device,fullpath);
	}else{
#ifdef never
		(void)printf("Output PS300 [%s]? ", fullpath);
#endif
		(void)printf("Output PS300 [%s]? ", DEV_DISPLAYLIST);
		(void)gets( device );		/* Null terminated */
		if( feof(stdin) )  exit(-1);
		if(device[0] != '_'){
			fprintf(stderr,"Mged: must specify one of %s\n", DEV_DISPLAYLIST);
			exit(-1);
		}
	}
	if( device[0] != '\0' )  {
		if( device[0] != '-' && device[1] != '\0' )  {
			fullpath = device;
			if( (psfd = open(device, O_RDWR)) < 0 )  {
				sprintf( device2, "/dev/pi%s%c", device, '\0' );
				fullpath = device2;
				if( (psfd = open(fullpath, O_RDWR)) < 0 )  {
					fullpath = device;
		                        if ((addr=gethostbyname(device)) == NULL){
	                			perror(fullpath);
						return(1);		/* BAD */
		                        }
				}
			}
		} else {
			/* Do nothing -- it will come out on stdout */
		}
	} else {
		if( (psfd = open(fullpath, O_RDWR)) < 0 )  {
			perror(fullpath);
			return(1);		/* BAD */
		}
	}
	close(psfd); psfd = -1;	/* for now */
	PAttach(fullpath);
	PInit();

	/* For all possible button presses in both button maps,
	 * build a table to function to button/light mappings.
	 */
	for( i=0; i < 12; i++ ) {
		if( (j = bviewmap[i]) != 0 )  {
			invmap[j].fm_viewmap = 1;/* view map */
			invmap[j].fm_button = i;
 			invmap[j].fm_blink = 0;
		}
		pgm_keytop( i, bviewmap[i] ); /* F0..F10, BV_xxx */
	}
	PSndStr( "(edit)", 1, "FLABEL12" );
	for( i=0; i < 12; i++ )  {
		if( (j = beditmap[i]) != 0 )  {
			invmap[j].fm_viewmap = 0;/* edit map */
			invmap[j].fm_button = i;
 			invmap[j].fm_blink = 0;
		}
	}

	/* A GENUINE PS300 Function Network */

	/* Create our own un-buffered host communications instance 
	 * for use with select running in the host. */
	PFnInst( "Ubhost_message", "concatenatec" );
	PConnect( "Ubhost_message", 1, 1, "hostout" );
	PSndStr( "\r", 2, "Ubhost_message" );

	/* DIALS 1..8 */
	for (i=1; i<=8; i++){
		sprintf(Dacc, "D%dacc", i);
		PFnInst(Dacc, "add");
		sprintf(Dname,"D%d_print",i);
		PFnInst(Dname,"print");
		sprintf(Cname,"D%dconcat",i);
		PFnInst(Cname,"cconcatenate");

		PConnect(Dname,1,2,Cname);
		PSndStr(sprintf(Dname1,"D%d ",i),1,Cname);
		PConnect(Cname,1,1,"UbHost_Message");
		PSndReal( 0.0, 2, Dacc );	/* Preset to zero */
		PConnect("Dials",i,1,Dacc);
		PConnect(Dacc, 1, 1, Dname);
		PConnect(Dacc, 1, 2, Dacc );	/* feedback loop */
	}
	/* Label the Dials */
	PSndStr( "Xslew", 1, "DLABEL1" );
	PSndStr( "Yslew", 1, "DLABEL2" );
	PSndStr( "1adc", 1, "DLABEL3" );
	PSndStr( "2adc", 1, "DLABEL4" );

	PSndStr( "Xrot", 1, "DLABEL5" );
	PSndStr( "Yrot", 1, "DLABEL6" );
	PSndStr( "Zrot", 1, "DLABEL7" );
	PSndStr( "Zoom", 1, "DLABEL8" );

	/* TABLET TRACKING always reports button #0 */
	PFnInst("T_track","cbroute");	/* tracking selector */
	PFnInst("T_trkprint","print");
	PFnInst("T_trkconcat","cconcatenate");
	PConnect("Tabletin",1,2,"T_track"); /* tablet tracking to select fctn */
	PConnect("T_track",1,1,"T_trkprint"); /* tracking x,y to ascii */
	PConnect("T_trkprint",1,2,"T_trkconcat");
	PSndStr("T0",1,"T_trkconcat");
	PConnect("T_trkconcat",1,1,"UbHost_Message");
	PSndBool(0, 1, "T_track");

	/* TABLET BUTTON PRESS with Coord */
	PFnInst("B_print","print");
	PFnInst("T_print","print");
	PFnInst("B_concat","cconcatenate");
	PFnInst("T_concat","cconcatenate");
	PConnect("Tabletin",3,1,"B_print");
	PConnect("Tabletin",6,1,"T_print"); /* press coord to ascii */
	PConnect("B_print",1,1,"B_concat");
	PConnect("T_print",1,2,"B_concat");
	PConnect("B_concat",1,2,"T_concat");
	PSndStr("T",1,"T_concat");
	PConnect("T_concat",1,1,"UbHost_Message");

	/* FUNCTION KEYS */
	PFnInst("F_print","print");
	PFnInst("F_concat","cconcatenate");
	PConnect("Fkeys",1,1,"F_print");
	PConnect("F_print",1,2,"F_concat");
	PSndStr("F",1,"F_concat");
	PConnect("F_concat",1,1,"UbHost_Message");

	/* MATRIX READBACK */
	PFnInstN("xf_sync","sync", 2);	/* f:sync(2) */
	PFnInst("xf_matrix","xformdata");
	PFnInst("xf_list","list");
	PFnInst("xf_trig","nop");
	PFnInst("xf_concat","cconcatenate");

	PConnect("xf_trig",1,1,"xf_sync");
	PConnect("xf_trig",1,2,"xf_matrix");
	PConnect("xf_sync",1,1,"xf_matrix");
	PConnect("xf_matrix",1,1,"xf_list");
	PConnect("xf_list",1,2,"xf_concat");
	PConnect("xf_concat",1,1,"UbHost_Message");
	PConnect("xf_list",1,2,"xf_sync");

	PSndBool(1,2,"xf_sync");
	PSndStr("M",1,"xf_concat");


 	/*
 	 *  This is the top-level node in the PS300 display tree
 	 *  (whatever E&S calls it).  Note that the matrixes
 	 *  "mmat" and "smat" are replaced by the prolog() routine.
 	 *  There are several inferior elements:  faceplate, model, and edit.
 	 */
 	PBeginS("world");
 	PInst( "", "faceplate" );
 	PViewP( "", -1.0,1.0, -1.0,1.0,	1.0,1.0, "" );
 	PWindow("mmat", -1.0,1.0, -1.0,1.0, 
 		1.0E-7,1.0E7, "");
 	PSetInt( "depth_cue", 1, 0.75, 1.0, "" );
 	PXfMatrx("model_mat", "model");
 	PWindow("smat", -1.0,1.0, -1.0,1.0, 
 		1.0E-7,1.0E7, "");
 	PSetInt( "sdepth_cue", 1, 1.0, 1.0, "" );
 	PXfMatrx("edit_mat", "edit");
 	PEndS();
 
	/* DISPLAY EVERYTHING */
	PDisplay( "world" );
	return(0);
}

/* F0..F11, BV_xxx */
pgm_keytop( key, bv )
{
	char cmd[32];

	if( bv <= 0 )  return;		/* undefined functions */
	if( bv >= BV_SHIFT )  return;	/* DM shift in/out */
	sprintf( cmd, "FLABEL%d%c", key+1, '\0' );
 	PSndBool( invmap[bv].fm_blink, 2, cmd );	/* blink state */
	PSndStr( label_button( bv ), 1, cmd );
}

/*
 *  			P S _ C L O S E
 *  
 *  Gracefully release the display.
 */
void
Ps_close()
{
	register int i;

	PInit();
	PDetach();
	psfd = -1;
}

/*
 *			P S _ R E S T A R T
 *
 * Used when the display processor wanders off.
 */
void
Ps_restart()
{
}

/*
 *			P S _ P R O L O G
 *
 * Define the world, and include in it instances of all the
 * important things.  Most important of all is the object "faceplate",
 * which is built between dmr_normal() and dmr_epilog()
 * by dmr_puts and dmr_2d_line calls from adcursor() and dotitles().
 */
void
Ps_prolog()
{
	static P_MatrixType m;

	if( !dmaflag )
		return;

	m[0][0] = model2view[0];
	m[1][0] = model2view[1];
	m[2][0] = model2view[2];
	m[3][0] = model2view[3];
	m[0][1] = model2view[4];
	m[1][1] = model2view[5];
	m[2][1] = model2view[6];
	m[3][1] = model2view[7];
	m[0][2] = model2view[8];
	m[1][2] = model2view[9];
	m[2][2] = model2view[10];
	m[3][2] = model2view[11];
	m[0][3] = model2view[12];
	m[1][3] = model2view[13];
	m[2][3] = model2view[14];
	m[3][3] = model2view[15];

	PSndM4d( m, 1, "world.mmat" );

	if( state == ST_VIEW )  return;

	m[0][0] = model2objview[0];
	m[1][0] = model2objview[1];
	m[2][0] = model2objview[2];
	m[3][0] = model2objview[3];
	m[0][1] = model2objview[4];
	m[1][1] = model2objview[5];
	m[2][1] = model2objview[6];
	m[3][1] = model2objview[7];
	m[0][2] = model2objview[8];
	m[1][2] = model2objview[9];
	m[2][2] = model2objview[10];
	m[3][2] = model2objview[11];
	m[0][3] = model2objview[12];
	m[1][3] = model2objview[13];
	m[2][3] = model2objview[14];
	m[3][3] = model2objview[15];

	PSndM4d( m, 1, "world.smat" );
}

/*
 *			P S _ E P I L O G
 */
void
Ps_epilog()
{
	/*
	 * A Point, in the Center of the Screen.
	 * This is drawn last, to always come out on top.
	 */
	Ps_2d_line( 0, 0, 0, 0, 0 );
	PEndS();			/* End of faceplate */
}

/*
 *  			P S _ N E W R O T
 *  Stub.
 */
void
Ps_newrot(mat)
mat_t mat;
{
	return;
}

/*
 *  			P S _ O B J E C T
 *  
 *  Set up for an object, transformed as indicated, and with an
 *  object center as specified.  The ratio of object to screen size
 *  is passed in as a convienience.  Mat is model2view.
 *
 *  Returns 0 if object could be drawn, !0 if object was omitted.
 */
int
Ps_object( sp, mat, ratio, white )
register struct solid *sp;
mat_t mat;
double ratio;
{
	return(1);	/* OK */
}

/*
 *			P S _ N O R M A L
 *
 * Restore the display processor to a normal mode of operation
 * (ie, not scaled, rotated, displaced, etc).
 * Turns off windowing.
 * Also, start the definition of the faceplate.
 * This BEGIN_STRUCTURE is matched by an END_STRUCTURE
 * produced by dmr_epilog().
 */
void
Ps_normal()
{
	PBeginS("faceplate");
	PWindow("", -1.,1., -1.,1., 1.0E-15,1.0E15, "");
	charscale = 1.0;
}

/*
 *			P S _ U P D A T E
 *
 * Transmit accumulated displaylist to the display processor.
 */
void
Ps_update()
{
	if( !dmaflag )
		return;

	PPurge();		/* flush GSR output buffers */
}

/*
 *			P S _ P U T S
 *
 * Output a string into the displaylist.
 * The starting position of the beam is as specified.
 */
void
Ps_puts( str, x, y, size, color )
register u_char *str;
{
	double scale;

	switch( size ) {
	case 0:
	default:
		scale = .015;
		break;
	case 1:
		scale = .025;
		break;
	case 2:
		scale = .05;
		break;
	case 3:
		scale = .07;
		break;
	}
	if( scale != charscale )  {
		PCharSca( "", scale/charscale, scale/charscale, "" );
		charscale = scale;	/* cvt relative to abs scaling */
	}
	PChars("", x/2048., y/2048., 0.0, 1.0, 0.0, str );
}

/*
 *			P S _ 2 D _ L I N E
 *
 */
void
Ps_2d_line( x1, y1, x2, y2, dashed )
int x1, y1;
int x2, y2;
int dashed;
{
	P_VectorType vector_list[2];

	vector_list[0].V4[0] = x1/2048.;
	vector_list[0].V4[1] = y1/2048.;
	vector_list[0].V4[2] = 0.0;
	vector_list[0].V4[3] = 1.;
	vector_list[0].Draw = 0;

	vector_list[1].V4[0] = x2/2048.;
	vector_list[1].V4[1] = y2/2048.;
	vector_list[1].V4[2] = 0.0;
	vector_list[1].V4[3] = 1.;
	vector_list[1].Draw = 1;

	PVecBegn( "", 2, 0, 0, 2, P_Item);	/* 2D */
	PVecList(2, vector_list);
	PVecEnd();
}

/*
 *			P S L I M I T
 *
 * Because the dials are so sensitive, setting them exactly to
 * zero is very difficult.  This function can be used to extend the
 * location of "zero" into "the dead zone".
 */
static float
pslimit(i)
double i;
{
#define NOISE	0.1

	if( i > NOISE )
		return( i-NOISE );
	if( i < -NOISE )
		return( i+NOISE );
	return(0.0);
}

/*
 *			P S _ I N P U T
 *
 * Execution must suspend in this routine until a significant event
 * has occured on either the command stream, or a device event has
 * occured, unless "noblock" is set.
 *
 * The GED "generic input" structure is filled in.
 *
 * Returns:
 *	0 if no command waiting to be read,
 *	1 if command is waiting to be read.
 */
Ps_input( cmd_fd, noblock )
{
	static long readfds;
	static struct timeval timeout;
	register int i, j;

#ifdef Magic
	PSndFix( 2, 1, "host_messageb"); /* set Send Immediate */
#endif
	PPurge();		/* Flush any pending output */
	/*
	 * Check for input on the keyboard or on the polled registers.
	 *
	 * Suspend execution until either
	 *  1)  User types a full line
	 *  2)  A change in peripheral status occurs
	 *  3)  The timelimit on SELECT has expired
	 *
	 * If a RATE operation is in progress (zoom, rotate, slew)
	 * in which the peripherals (rate setting) may not be changed,
	 * but we still have to update the display,
	 * do not suspend execution.
	 */
	if( noblock )  {
		/* Don't flood the sluggish matrix update */
		timeout.tv_sec = 0;
		timeout.tv_usec = 50000;
/*		timeout.tv_usec = 300000; */
	}  else  {
		timeout.tv_sec = 1000;
		timeout.tv_usec = 0;
	}

	readfds = (1<<cmd_fd) | (1<<xt_chan);
	i = select( 32, &readfds, 0L, 0L, &timeout );

	/*
	 * Set device interface structure for GED to "rest" state.
	 * First, process any messages that came in.
	 */
	dm_values.dv_buttonpress = 0;
	dm_values.dv_flagadc = 0;
	dm_values.dv_penpress = 0;

	if( readfds & (1<<xt_chan) )  {
		pscheck();
	}

	if( readfds & (1<<0) )
		return(1);		/* command awaits */
	else
		return(0);		/* just peripheral stuff */
}

/* 
 *			P S _ L I G H T
 *
 * This function must keep both the light hardware, and the software
 * copy of the lights up to date.  Note that requests for light changes
 * may not actually cause the lights< to be changed, depending on
 * whether the buttons are being used for "view" or "edit" functions
 * (although this is not done in the present code).
 */
void
Ps_light( cmd, func )
int cmd;
int func;			/* BE_ or BV_ function */
{
	register int i;

	if( invmap[func].fm_viewmap != bviewflag )
		return;		/* Should save state for SHIFT_BUTTON chg */
	i = invmap[func].fm_button;
	switch( cmd )  {
	case LIGHT_RESET:
		/* Fall through */
	case LIGHT_OFF:
 		invmap[func].fm_blink = 0;	/* no blink */
		break;
	case LIGHT_ON:
 		invmap[func].fm_blink = 1;	/* blink */
		break;
	}
 		pgm_keytop( i, func );
}

#define BUFSIZE		10000		/* # of displaylist elements */
static P_VectorType	storage[BUFSIZE];/* Building area for solids */

/*
 *			P S _ C V T V E C S
 *
 * Convert model space vector list to PictureSystem 300 displaylist, and
 * calculate displaylist memory requirement.
 * Returns # of bytes actually consumed in displaylist buffer.
 * The co-ordinate system extends from +full scale to
 * -full scale, as the object extends from xmin to xmax.
 * Hence the total co-ordinate distance is 2*full scale.
 *
 * Note that these macros draw the object FULL SCALE.
 * (Ie to occupy the full screen).  dozoom() depends on this.
 *
 *	delta * full scale / (scale/2)
 * gives delta * full scale * 2 / scale
 *
 */
#ifdef never
#define XCVT(x)		( (((x) - sp->s_center[0]) * factor ))
#define YCVT(y)		( (((y) - sp->s_center[1]) * factor ))
#define ZCVT(z)		( (((z) - sp->s_center[2]) * factor ))
#endif

unsigned
Ps_cvtvecs( sp )
register struct solid *sp;
{
	register P_VectorType	*output;
	register struct veclist *vp;
	register int nvec;
	char oname[32];

	sprintf( oname,"O%x%c", sp, '\0' );

	output = &storage[0];

	nvec = sp->s_vlen;
	for( vp = sp->s_vlist; nvec-- > 0; vp++ )  {
		/* PS300 is Left-Handed.  Z sign is wrong, but ... */
		output->V4[0] = ( vp->vl_pnt[0] );
		output->V4[1] = ( vp->vl_pnt[1] );
		output->V4[2] = ( vp->vl_pnt[2] );
		output->V4[3] = 1.0;
		if( vp->vl_pen == PEN_UP )  {
			/* Move, not draw */
			output->Draw = 0;
		}  else  {
			/* draw */
			output->Draw = 1;
		}
		if( output++ >= &storage[BUFSIZE-4] )  {
			(void)printf("Ps_cvtvecs:  Displaylist truncated\n");
			break;
		}
	}

	PVecBegn( oname, sp->s_vlen, 0, 0, 3, P_Item );
	PVecList( sp->s_vlen, storage );
	PVecEnd();
	if( sp->s_soldash )
		PPatWith( oname, "dotdashed" );

	return( 0 );	/* No "displaylist" consumed */
}

/*
 * Loads displaylist from storage[]
 */
unsigned
Ps_load( addr, count )
unsigned addr, count;
{
	return( 0 );		/* FLAG:  error */
}

void
pscheck()
{
	char buf[1024];
	register int i,j;
	auto double d1, d2;	/* for scanf */
	auto int i1;		/* for scanf */

top:
	if( (i = psPGet( buf, sizeof(buf) )) <= 0 )
		return;
	buf[i] = '\0';

	if( ps_debug )  {
		(void)printf("dm-ps: %s\n", buf );
	}

	/* Message from PS300 function network is 1 of 3 formats:
	 *  	T#V x,y		tablet button # and coordinates
	 *  	F#		function key
	 *  	D# v		dial # and value
	 *	M		PS300 transformation matrix
	 */
	switch( buf[0] )  {
	case 'T':
		/* PS300 provides values in range of -1 to +1;
		 *  desired range is  -2048 <= x,y <= +2048.
		 *  Also, PS300 only sends position on a press.
		 */
		sscanf( &buf[3], "%f,%f", &d1, &d2 );
		dm_values.dv_xpen = (int)(d1 * 2048.);
		dm_values.dv_ypen = (int)(d2 * 2048.);
		i = atoi(&buf[1]);	/* Tracking update only */
		if( i == 0 )
			break;		/* See if more tracking info */

		if( i & 1 )		/* pen press or main button */
			dm_values.dv_penpress = DV_PICK;
		else if( i & 2 )	/* left button */
			dm_values.dv_penpress = DV_INZOOM;
		else if( i & 4 )	/* middle */
			dm_values.dv_penpress = DV_SLEW;
		else if( i & 8 )	/* right button */
			dm_values.dv_penpress = DV_OUTZOOM;
		return;			/* Force top to see this event */
	case 'F':
		i = atoi( &buf[1] ) - 1;
		if( i == SHIFT_BUTTON )  {
			if( bviewflag )  {
				bviewflag = 0;
				/* send edit labels */
				for( i=0; i<11; i++ )
 					pgm_keytop( i, beditmap[i] );
				PSndStr( "(view)", 1, "FLABEL12" );
			} else {
				bviewflag = 1;
				/* send view labels */
				for( i=0; i<11; i++ )
 					pgm_keytop( i, bviewmap[i] );
				PSndStr( "(edit)", 1, "FLABEL12" );
			}
			break;
		} else if( bviewflag )
			dm_values.dv_buttonpress = bviewmap[i];
		else
			dm_values.dv_buttonpress = beditmap[i];
		break;
	case 'D':
		sscanf( &buf[1], "%d %f", &i1, &d1 );
		switch( i1 )  {
		case 1:
			dm_values.dv_xslew = pslimit(d1);
			break;
		case 2:
			dm_values.dv_yslew = pslimit(d1);
			break;
		case 3:
			dm_values.dv_1adc = pslimit(d1);
			dm_values.dv_flagadc = 1;
			break;
		case 4:
			dm_values.dv_2adc = pslimit(d1);
			dm_values.dv_flagadc = 1;
			break;
		case 5:
			dm_values.dv_xjoy = pslimit(d1);
			break;
		case 6:
			dm_values.dv_yjoy = pslimit(d1);
			break;
		case 7:
			dm_values.dv_zjoy = pslimit(d1);
			break;
		case 8:
			dm_values.dv_zoom = pslimit(d1);
			break;
		default:
			(void)printf("dm-ps: bad knob %d\n", i1 );
			return;
		}
		break;
	default:
		(void)printf("dm-ps:  PS300 said '%s'??\n", buf );
		break;
	}
	goto top;
}

void
Ps_statechange( a, b )
{
	register struct solid *sp;

	/*
	 *  Based upon new state, possibly do extra stuff,
	 *  including enabling continuous tablet tracking,
	 *  object highlighting, and transmitting the separate
	 *  "edit" display group (for solid & object editing).
	 */
	switch( b )  {
	case ST_VIEW:
		PSndBool(0, 1, "T_track");	/* constant tracking OFF */
		PickPath = FALSE;
		Ps_viewchange(DM_CHGV_REDO, SOLID_NULL);
		break;
		
	case ST_S_PICK:
	case ST_O_PICK:
	case ST_O_PATH:
		PSndBool(1, 1, "T_track");	/* constant tracking ON */
		PickPath = TRUE;
		Ps_viewchange(DM_CHGV_REDO, SOLID_NULL );
		break;
	case ST_O_EDIT:
	case ST_S_EDIT:
		/*** BANDAID - make sure solid is illuminated ***/
		illump->s_iflag = UP;

		PSndBool(0, 1, "T_track");	/* constant tracking OFF */
		PickPath = FALSE;
		Ps_viewchange(DM_CHGV_REDO, SOLID_NULL );
		break;
	default:
		(void)printf("Ps_statechange: unknown state %s\n", state_str[b]);
		break;
	}

}

/*
 *  Patterned after E&S Library routine PGet, but depends on
 *  arcane magic being performed on host_messageB, plus
 *  USNA/BRL select() capability in the driver to awaken us.
 */
#include <ps300/pidefs.h>
psPGet( buf, buflen )
char *buf;
{
	register int    n;
	register int _vms_err;		/* ghastly */
	extern int xt_dev_type;
	struct vms_iosb iosb;

	switch(xt_dev_type){
#ifdef never
	case PDV_DEVPARALLEL:
		_vms_err = piqiow(0,xt_chan,PI_IO_RDI_NET,&iosb,0,0,buf,buflen,0);
		if (_vms_err == SS_NORMAL) {
			n = iosb.iosb_devdata & 0xFFFF;
			if ((n > 0) && (buf[n - 1] == '\r'))
				n--;
			return(n);
		}
		printf("psPGet -1, vms_err=%d\n", _vms_err);
		break;
#endif
	case PDV_DEVNETPS300:
		n = PGet(buf,buflen-4);
		if(ps_debug)
			printf("psPget: net read= %d\n",n);
		if( ! strcmp(buf,"\r") )
			return(0);
		return(n);
	}
	return(-1);
}

void
Ps_viewchange( cmd, nsp )
register int cmd;
register struct solid *nsp;
{
	static P_PatternType pattern = { 1, 1, 3, 1 }; /* dot dashed */
	register struct solid *sp;
	double interval;
	char str[32];

	/* define "dotdashed" pattern */
	interval = Viewscale/100.0;
	if( interval < 5.0 || interval > 1000.0 )  interval = 50.0;
	PDefPatt( "dotdashed", 4, pattern, 1, 1, interval );

	switch( cmd )  {
	case DM_CHGV_ADD:
		sprintf( str, "O%x%c", nsp, '\0' );
		PIncl( str, "model" );
		break;
	case DM_CHGV_ILLUM:
		if(PickPath){
			PInst( "edit" );
			sprintf( str, "O%x%c", nsp, '\0' );
			PIncl(str, "edit" );
			break;
		}
		/* If not PickPath, then fall through */
	case DM_CHGV_REDO:
		/* Need to erase and rebuild 'model' and 'edit' */
		PInst( "model", "" );
	 	PInst( "edit", "" );
		FOR_ALL_SOLIDS( sp )  {
			sprintf( str, "O%x%c", sp, '\0' );
			if( sp->s_iflag != UP ){
				PIncl( str, "model" );	/* In model */
			}else{
				PIncl( str, "edit" );	/* In edit */
				if( sp == illump )  {
					/* Intensify by doing twice */
					PIncl( str, "edit" );
				}
			}
		}
		break;
	case DM_CHGV_DEL:
		/* Delete an object from the display */
		sprintf( str, "O%x%c", nsp, '\0' );
		PRemFrom(str, "model");
		break;
	case DM_CHGV_REPL:
		break;
	default:
		(void)printf("dm-ps: Unknown viewchange state: %d\n",cmd);
	}
	PPurge();
}

void
Ps_colorchange()
{
	/* Not until we get color PS300's */
}

void
Ps_window(w)
int w[];
{
	register int i;
	for( i=0; i<6; i++ )
		ps_window[i] = w[i];
}

void
Ps_debug(lvl)
{
	ps_debug = lvl;
}
