/*
 * Copyright (c) 1999, 2000 Greg Haerr <greg@censoft.com>
 * Portions Copyright (c) 1991 David I. Bell
 * Permission is granted to use, distribute, or modify this source,
 * provided that this copyright notice remains intact.
 *
 * UNIX Serial Port Mouse Driver
 * 
 * This driver opens a serial port directly, and interprets serial data.
 * Microsoft, PC, Logitech and PS/2 mice are supported.
 * The PS/2 mouse is supported by using the /dev/psaux device.
 *
 * The following environment variables control the mouse type expected
 * and the serial port to open.
 *
 * Environment Var	Default		Allowed
 * MOUSE_TYPE		pc		ms, pc, logi, ps2
 * MOUSE_PORT		/dev/ttyS1	any serial port or /dev/psaux
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include "device.h"

#define TERMIOS		1	/* set to use termios serial port control*/
#define SGTTY		0	/* set to use sgtty serial port control*/

#define	SCALE		3	/* default scaling factor for acceleration */
#define	THRESH		5	/* default threshhold for acceleration */

#if TERMIOS
#include <termios.h>
#endif
#if SGTTY
#include <sgtty.h>
#endif

/* default settings*/
#if _MINIX
#define	MOUSE_PORT	"/dev/mouse"
#define	MOUSE_TYPE	"ms"
#else
#if ELKS
#define	MOUSE_PORT	"/dev/ttys0"	/* default mouse tty port */
#else
#define	MOUSE_PORT	"/dev/ttyS1"	/* default mouse tty port */
#endif
#define	MOUSE_TYPE	"pc"		/* default mouse type "ms","pc","ps2" */
#endif
#define MAX_BYTES	128		/* number of bytes for buffer */

/* states for the mouse*/
#define	IDLE			0		/* start of byte sequence */
#define	XSET			1		/* setting x delta */
#define	YSET			2		/* setting y delta */
#define	XADD			3		/* adjusting x delta */
#define	YADD			4		/* adjusting y delta */

/* values in the bytes returned by the mouse for the buttons*/
#define	PC_LEFT_BUTTON		4
#define PC_MIDDLE_BUTTON	2
#define PC_RIGHT_BUTTON		1

#define	MS_LEFT_BUTTON		2
#define MS_RIGHT_BUTTON		1

#define PS2_CTRL_BYTE		0x08
#define PS2_LEFT_BUTTON		1
#define PS2_RIGHT_BUTTON	2

/* Bit fields in the bytes sent by the mouse.*/
#define TOP_FIVE_BITS		0xf8
#define BOTTOM_THREE_BITS	0x07
#define TOP_BIT			0x80
#define SIXTH_BIT		0x40
#define BOTTOM_TWO_BITS		0x03
#define THIRD_FOURTH_BITS	0x0c
#define BOTTOM_SIX_BITS  	0x3f

/* local data*/
static int		mouse_fd;	/* file descriptor for mouse */
static int		state;		/* IDLE, XSET, ... */
static int		buttons;	/* current mouse buttons pressed*/
static int		availbuttons;	/* which buttons are available */
static MWCOORD		xd;		/* change in x */
static MWCOORD		yd;		/* change in y */

static int		left;		/* because the button values change */
static int		middle;		/* between mice, the buttons are */
static int		right;		/* redefined */

static unsigned char	*bp;		/* buffer pointer */
static int		nbytes;		/* number of bytes left */
static unsigned char	buffer[MAX_BYTES];	/* data bytes read */
static int		(*parse)();	/* parse routine */

/* local routines*/
static int  	MOU_Open(MOUSEDEVICE *pmd);
static void 	MOU_Close(void);
static int  	MOU_GetButtonInfo(void);
static void	MOU_GetDefaultAccel(int *pscale,int *pthresh);
static int  	MOU_Read(MWCOORD *dx, MWCOORD *dy, MWCOORD *dz, int *bptr);
static int	MOU_Poll(void);
static int  	ParsePC(int);		/* routine to interpret PC mouse */
static int  	ParseMS(int);		/* routine to interpret MS mouse */
static int  	ParsePS2(int);		/* routine to interpret PS/2 mouse */

MOUSEDEVICE mousedev = {
	MOU_Open,
	MOU_Close,
	MOU_GetButtonInfo,
	MOU_GetDefaultAccel,
	MOU_Read,
#if _MINIX
	MOU_Poll
#else
	NULL
#endif
};

/*
 * Open up the mouse device.
 * Returns the fd if successful, or negative if unsuccessful.
 */
static int
MOU_Open(MOUSEDEVICE *pmd)
{
	char	*type;
	char	*port;
	struct termios termios;

	/* get mouse type and port*/
	if( !(type = getenv("MOUSE_TYPE")))
		type = MOUSE_TYPE;

	if( !(port = getenv("MOUSE_PORT")))
		port = MOUSE_PORT;

	/* set button bits and parse procedure*/
	if(!strcmp(type, "pc") || !strcmp(type, "logi")) {
		/* pc or logitech mouse*/
		left = PC_LEFT_BUTTON;
		middle = PC_MIDDLE_BUTTON;
		right = PC_RIGHT_BUTTON;
		parse = ParsePC;
	} else if (strcmp(type, "ms") == 0) {
		/* microsoft mouse*/
		left = MS_LEFT_BUTTON;
		right = MS_RIGHT_BUTTON;
		middle = 0;
		parse = ParseMS;
	} else if (strcmp(type, "ps2") == 0) {
		/* PS/2 mouse*/
		left = PS2_LEFT_BUTTON;
		right = PS2_RIGHT_BUTTON;
		middle = 0;
		parse = ParsePS2;
	} else
		return -1;

	/* open mouse port*/
	mouse_fd = open(port, O_NONBLOCK);
	if (mouse_fd < 0) {
		EPRINTF("Error %d opening serial mouse type %s on port %s.\n",
			errno, type, port);
 		return -1;
	}

#if SGTTY
	/* set rawmode serial port using sgtty*/
	struct sgttyb sgttyb;

	if (ioctl(fd, TIOCGETP, &sgttyb) == -1)
		goto err;
	sgttyb.sg_flags |= RAW;
	sgttyb.sg_flags &= ~(EVENP | ODDP | ECHO | XTABS | CRMOD);

	if (ioctl(fd, TIOCSETP, &sgttyb) == -1)
		goto err;
	if (ioctl(fd, TIOCFLUSH, 0) < 0)
		goto err;
#endif

#if TERMIOS
	/*
	 * Note we don't check success for the tcget/setattr calls,
	 * some kernels don't support them for certain devices
	 * (like /dev/psaux).
	 */

	/* set rawmode serial port using termios*/
	tcgetattr(mouse_fd, &termios);

	/* These functions appear to be broken in ELKS Dev86 */
	if(cfgetispeed(&termios) != B1200)
		cfsetispeed(&termios, B1200);
#if _MINIX
	if(cfgetospeed(&termios) != B1200)
		cfsetospeed(&termios, B1200);
#endif

#if !_MINIX
	termios.c_cflag &= ~CBAUD;
	termios.c_cflag |= B1200;
#endif
	termios.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
	termios.c_iflag &= ~(ICRNL | INPCK | ISTRIP | IXON | BRKINT | IGNBRK);
	termios.c_cflag &= ~(CSIZE | PARENB);
	termios.c_cflag |= CS8;
	termios.c_cc[VMIN] = 0;
	termios.c_cc[VTIME] = 0;

	tcsetattr(mouse_fd, TCSAFLUSH, &termios);
#endif /* TERMIOS*/

	/* initialize data*/
	availbuttons = left | middle | right;
	state = IDLE;
	nbytes = 0;
	buttons = 0;
	xd = 0;
	yd = 0;
	return mouse_fd;
err:
	close(mouse_fd);
	mouse_fd = 0;
	return -1;
}

/*
 * Close the mouse device.
 */
static void
MOU_Close(void)
{
	if (mouse_fd > 0) {
		close(mouse_fd);
	}
	mouse_fd = 0;
}

/*
 * Get mouse buttons supported
 */
static int
MOU_GetButtonInfo(void)
{
	return availbuttons;
}

/*
 * Get default mouse acceleration settings
 */
static void
MOU_GetDefaultAccel(int *pscale,int *pthresh)
{
	*pscale = SCALE;
	*pthresh = THRESH;
}

/*
 * Attempt to read bytes from the mouse and interpret them.
 * Returns -1 on error, 0 if either no bytes were read or not enough
 * was read for a complete state, or 1 if the new state was read.
 * When a new state is read, the current buttons and x and y deltas
 * are returned.  This routine does not block.
 */
static int
MOU_Read(MWCOORD *dx, MWCOORD *dy, MWCOORD *dz, int *bptr)
{
	int	b;

	/*
	 * If there are no more bytes left, then read some more,
	 * waiting for them to arrive.  On a signal or a non-blocking
	 * error, return saying there is no new state available yet.
	 */
	if (nbytes <= 0) {
		bp = buffer;
		nbytes = read(mouse_fd, bp, MAX_BYTES);
		if (nbytes < 0) {
			if (errno == EINTR || errno == EAGAIN)
				return 0;
#if _MINIX
			return 0;
#else
			return -1;
#endif
		}
	}

	/*
	 * Loop over all the bytes read in the buffer, parsing them.
	 * When a complete state has been read, return the results,
	 * leaving further bytes in the buffer for later calls.
	 */
	while (nbytes-- > 0) {
		if ((*parse)((int) *bp++)) {
			*dx = xd;
			*dy = yd;
			*dz = 0;
			b = 0;
			if(buttons & left)
				b |= MWBUTTON_L;
			if(buttons & right)
				b |= MWBUTTON_R;
			if(buttons & middle)
				b |= MWBUTTON_M;
			*bptr = b;
			return 1;
		}
	}
	return 0;
}

/*
 * Input routine for PC mouse.
 * Returns nonzero when a new mouse state has been completed.
 */
static int
ParsePC(int byte)
{
	int	sign;			/* sign of movement */

	switch (state) {
		case IDLE:
			if ((byte & TOP_FIVE_BITS) == TOP_BIT) {
				buttons = ~byte & BOTTOM_THREE_BITS;
				state = XSET;
			}
			break;

		case XSET:
			sign = 1;
			if (byte > 127) {
				byte = 256 - byte;
				sign = -1;
			}
			xd = byte * sign;
			state = YSET;
			break;

		case YSET:
			sign = 1;
			if (byte > 127) {
				byte = 256 - byte;
				sign = -1;
			}
			yd = -byte * sign;
			state = XADD;
			break;

		case XADD:
			sign = 1;
			if (byte > 127) {
				byte = 256 - byte;
				sign = -1;
			}
			xd += byte * sign;
			state = YADD;
			break;

		case YADD:
			sign = 1;
			if (byte > 127) {
				byte = 256 - byte;
				sign = -1;
			}
			yd -= byte * sign;
			state = IDLE;
			return 1;
	}
	return 0;
}


/*
 * Input routine for Microsoft mouse.
 * Returns nonzero when a new mouse state has been completed.
 */
static int
ParseMS(int byte)
{
	switch (state) {
		case IDLE:
			if (byte & SIXTH_BIT) {
				buttons = (byte >> 4) & BOTTOM_TWO_BITS;
				yd = ((byte & THIRD_FOURTH_BITS) << 4);
				xd = ((byte & BOTTOM_TWO_BITS) << 6);
				state = XADD;
			}
			break;

		case XADD:
			xd |= (byte & BOTTOM_SIX_BITS);
			state = YADD;
			break;

		case YADD:
			yd |= (byte & BOTTOM_SIX_BITS);
			state = IDLE;
			if (xd > 127)
				xd -= 256;
			if (yd > 127)
				yd -= 256;
			return 1;
	}
	return 0;
}

/*
 * Input routine for PS/2 mouse.
 * Returns nonzero when a new mouse state has been completed.
 */
static int
ParsePS2(int byte)
{
	switch (state) {
		case IDLE:
			if (byte & PS2_CTRL_BYTE) {
				buttons = byte & 
					(PS2_LEFT_BUTTON|PS2_RIGHT_BUTTON);
				state = XSET;
			}
			break;

		case XSET:
			if(byte > 127)
				byte -= 256;
			xd = byte;
			state = YSET;
			break;

		case YSET:
			if(byte > 127)
				byte -= 256;
			yd = -byte;
			state = IDLE;
			return 1;
	}
	return 0;
}

static int
MOU_Poll(void)
{
	return 1;	/* used by _MINIX only*/
}

/*  #define TEST 1  */
#if TEST
main()
{
	MWCOORD x, y, z;
	int	b;

	DPRINTF("Open Mouse\n");
	if( MOU_Open(0) < 0)
		DPRINTF("open failed mouse\n" );

	while(1) 
	{
		if(MOU_Read(&x, &y, &z, &b) == 1) 
		{
	     		DPRINTF("%d,%d,%d\n", x, y, b);
		}
	}
}
#endif
