static char rcsid[] = "$Header: mouse.c,v 800.0 85/08/06 14:17:11 root Exp $";
static char sccsid[] = "%W% %Y% %Q% %G%";
/*
 * mouse line discipline
 * 840906 sbs
 *
 * This line explicitly does not allow for
 *	- something other than a mouse to be attached to acia#1
 *	- a device file to read the mouse directly
 * Rather, it is always used in conjunction with the console: if enabled,
 * it merges mouse button presses with keyboard input; if disabled, it
 * just throws away mouse button presses.  The idea is to create a "virtual
 * device" -- the user -- which presents all input to programs in the order
 * that the user created it, with a minimum of fuss.
 *
 * 850707 sam
 * changed flushtty() call to ttyflush() call.
 *
 */

#include "../h/param.h"			/* system types and constants */
#include "../h/conf.h"			/* *devsw, &c. */
#include "../h/errno.h"			/* system error numbers */
#include "../h/file.h"			/* eh? */
#include "../h/ioctl.h"			/* ioctl interface */
#include "../h/tty.h"			/* tty types and definitions */
#include "../white/mouse.h"		/* device(MOUSE) specific stuff */

/*
 * brute-force around showmatch
 */
#define LBRACE '{'
#define RBRACE '}'

/*
 * States of the machine.
 */
#define M_LOCKOUT (-1)		/* locked out */
#define	M_INIT	0
#define M_GETX1	1
#define M_GETY1 2
#define M_GETX2 3
#define M_GETY2 4
#define M_LAST	M_GETY2
int	mouse_state = M_INIT;
int	mnext_state = M_INIT;
struct	graphics_cursor	gcursor,
			ogcursor;
int	mouse_flags;
int	mouse_button,
	oldm_button;
/*
 * XXX debugging
 */
int	mouse_n,	/* how many mouse sequences processed */
	mouse_overrun;	/* how many were overruns */
extern	struct	tty	ac_tty[],
			co_tty[];

/*
 * (*linesw[m->t_line].l_rint)(c, m)
 *	only called when m->t_state&TS_ISOPEN
 */
mouse_input(ch, m)
	char	ch;	/* ugh.  acia returns u_char, must force sign here */
	struct tty *m;
{
	static short dx, dy;
	register short c = ch;

	if ((mouse_flags&DISABLE_MOUSE) || mouse_state < M_INIT)
		return;
	if (mouse_flags&PRINT_THROUGH) {
		coinput(c);
		return;
	}
	/*
	 * XXX hack -- in case we get a mouse overrun, ignore the old
	 * sequence and restart the new one.
	 */
	if ((c&MSYNCMASK) == MSYNC) {
		if (mouse_state > M_INIT)
			mouse_overrun++;
		mouse_state = M_INIT;
	}

	switch (mouse_state) {
	case M_INIT:
		if ((c&MSYNCMASK) == MSYNC) {
			mouse_n++;
			mouse_state = M_GETX1;
			oldm_button = mouse_button;
			mouse_button = (~c)&BUTTON_BITS;
			ogcursor = gcursor;
		}
		break;
	case M_GETX1:
		dx = c;
		mouse_state = M_GETY1;
		break;
	case M_GETY1:
		dy = c;
		mouse_state = M_GETX2;
		break;
	case M_GETX2:
		dx += c;
		mouse_state = M_GETY2;
		break;
	case M_GETY2: {
		register short e;

		dy += c;
		e = gcursor.cur_x + dx;
		if (e > XMAX)
			e = XMAX;
		else if (e < XMIN)
			e = XMIN;
		gcursor.cur_x = e;
		e = gcursor.cur_y + dy;
		if (e > YMAX)
			e = YMAX;
		else if (e < YMIN)
			e = YMIN;
		gcursor.cur_y = e;
		/*
		 * Show the cursor if necessary
		 */
		if ((mouse_flags&INVIS_CURSOR) == 0) {
			dsgcursor(ogcursor.cur_x, ogcursor.cur_y);
			dsgcursor(gcursor.cur_x, gcursor.cur_y);
		}
		/*
		 * If the button press changed, present it to the user
		 */
		if (mouse_button && mouse_button != oldm_button) {
			register struct tty *tp = &co_tty[0];
			register tflags = tp->t_flags;

			/*
			 * Must be able to stuff all 8 bytes into the
			 * input q.  If can't, beep.
			 * (code looted from ttyinput)
			 */
			if ((tflags&RAW) && tp->t_rawq.c_cc >= TTYHOG-8)
				ttyflush(tp, FREAD|FWRITE);
			else if ((tflags&CBREAK) && tp->t_rawq.c_cc>=TTYHOG-8) {
				if (tp->t_line == NTTYDISC &&
				    tp->t_outq.c_cc < TTHIWAT(tp))
					ttyoutput(CTRL(g), tp);
			} else if (tp->t_rawq.c_cc+tp->t_canq.c_cc>=TTYHOG-8) {
				if (tp->t_line == NTTYDISC)
					ttyoutput(CTRL(g), tp);
			} else {
				/*
				 * This table encodes WPG's algorithm for button
				 * priorities.  Talk to him if it's wrong.
				 * The table index is the button press; the
				 * value of each entry is the button expected
				 * by ged.
				 */
				static char belly[] = {
					0,     4+' ', 8+' ', 1+' ',
					1+' ', 1+' ', 1+' ', 1+' '
				};

				/*
				 * should enable tp->t_rsel if being
				 * selected here!
				 */
				tp->t_flags &= ~ECHO;
				coinput('~');
				coinput(RBRACE);
				coinput((gcursor.cur_x>>6) + ' ');
				coinput((gcursor.cur_x&0x3F) + ' ');
				coinput((gcursor.cur_y>>6) + ' ');
				coinput((gcursor.cur_y&0x3F) + ' ');
				coinput(belly[mouse_button]);
				tp->t_flags = tflags;	/* reenable echo here */
				coinput('\n'); /* so that we get vg kludge \n */
			}
		}
		mouse_state = mnext_state;
		wakeup(&mouse_state);
		break;
	}
	}
}

/*
 * (*linesw[tp->t_line].l_open)(dev, uio) is nodev()
 * (*linesw[tp->t_line].l_close)(dev, uio) is nulldev()
 * (*linesw[tp->t_line].l_read)(dev, uio) is nodev()
 * (*linesw[tp->t_line].l_write)(dev, uio) is nodev()
 * (*linesw[tp->t_line].l_ioctl)(dev, uio) is nodev()
 */

/*
 * called by coclose to close the mouse
 */
mouse_close(tp)
	register struct tty *tp;
{
	int	x = 0;
	mouse_ioctl(tp, TIOCMERGE, &x, 0);
}

int lock_mouse();
#define unlock_mouse() (mouse_state = M_INIT)
#define check_open(m) \
		if (((m)->t_state&TS_ISOPEN) == 0) \
			return ENOTTY;
int	mrefcnt = 0;
/*
 * called by coioctl to open, close, and alter state on
 * the mickey
 */
mouse_ioctl(tp, cmd, addr, flag)
	register struct tty *tp;
	register caddr_t addr;
{
	register struct tty *m = &ac_tty[1];

	/*
	 * For each command, the scam is:
	 *	Call check_open() to see if the mouse is active:
	 *		if not, bomb out of this routine;
	 *	Call lock_mouse() to protect the mouse from interrupts;
	 *	Do the action specified by the comand:
	 *		if it's an illegal action, bomb out;
	 *	Unlock the mouse and return.
	 */
	switch (cmd) {
	case TIOCMERGE: {
		register options = *(int *)addr;

		lock_mouse();
		if (options == 0) {    /* a mouse close */
			if ((m->t_state&TS_ISOPEN) && --mrefcnt == 0) {
				if ((mouse_flags&INVIS_CURSOR) == 0)
					dsgcursor(gcursor.cur_x, gcursor.cur_y);
				acclose(1, 1);
				m->t_line = 0; m->t_state = 0;
			}
		} else if (mouse_flags == 0) {    /* a mouse open */
			if (mrefcnt++ == 0) {		/* first open */
				oldm_button = 0;
				gcursor.cur_x = (XMAX+1)/2;
				gcursor.cur_y = (YMAX+1)/2;
				if ((options&INVIS_CURSOR) == 0)
					dsgcursor(gcursor.cur_x, gcursor.cur_y);
				acopen(1, 1); /* set baud, enable ints, &c */
				m->t_line = MOUSELDISC;
			}
		} else if ((options&INVIS_CURSOR) != (mouse_flags&INVIS_CURSOR))
			/* toggle the gcursor */
			dsgcursor(gcursor.cur_x, gcursor.cur_y);
		mouse_flags = options;
		break;
	}
	case TIOCGCURSOR:		/* read out current graph cur pos */
		check_open(m);	/* return ENOTTY if mouse isn't open */
		lock_mouse();
		*(struct graphics_cursor *)addr = gcursor;
		break;
	case TIOCSCURSOR:		/* set current graph cur pos */
		check_open(m);	/* return ENOTTY if mouse isn't open */
		lock_mouse();
		ogcursor = gcursor;
		gcursor = *(struct graphics_cursor *)addr;
		if ((mouse_flags&INVIS_CURSOR) == 0) {
			dsgcursor(ogcursor.cur_x, ogcursor.cur_y);
			dsgcursor(gcursor.cur_x, gcursor.cur_y);
		}
		break;
	default:		/* don't grok it; let the next guy try */
		return -1;
	}
	unlock_mouse();
	return 0;
}

/*
 * lock out the mouse.  If in the middle of a sequence, wait until
 * the sequence completes; otherwise, grab it right away.
 */
lock_mouse() {
	register s;

	mnext_state = M_LOCKOUT;
	s = spl5();
	while (mouse_state > M_INIT)	/* while in a sequence */
		sleep(&mouse_state, PZERO+1);
	mouse_state = M_LOCKOUT;
	mnext_state = M_INIT;
	splx(s);
}
