/* simple driver for serial mouse */
/* Andrew Haylett, 14th December 1992 */
/* Edited for svgalib (hhanemaa@cs.ruu.nl). */
/* This probably doesn't work with all types of bus mouse. */

/* This file is included by mouse.c. */

#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <time.h>


/* #define DEBUG */

static int mtype;
static int mbaud = 1200;	/* Should be 1200. */
static int msample;
static char *mdev;
static int mfd = -1;
static int mfdmode = 0;		/* 0 means don't wait (NDELAY) */


static const unsigned short cflag[5] =
{
      (CS7                   | CREAD | CLOCAL | HUPCL ),   /* MicroSoft */
      (CS8 | CSTOPB          | CREAD | CLOCAL | HUPCL ),   /* MouseSystems */
      (CS8 | PARENB | PARODD | CREAD | CLOCAL | HUPCL ),   /* MMSeries */
      (CS8 | CSTOPB          | CREAD | CLOCAL | HUPCL ),   /* Logitech */
      0                                                    /* BusMouse */
};

static const unsigned char proto[5][5] =
{
    /*  hd_mask hd_id   dp_mask dp_id   nobytes */
    { 	0x40,	0x40,	0x40,	0x00,	3 	},  /* MicroSoft */
    {	0xf8,	0x80,	0x00,	0x00,	5	},  /* MouseSystems */
    {	0xe0,	0x80,	0x80,	0x00,	3	},  /* MMSeries */
    {	0xe0,	0x80,	0x80,	0x00,	3	},  /* Logitech */
    {	0xf8,	0x80,	0x00,	0x00,	5	}   /* BusMouse */
};

static void
ms_setspeed(const int old, const int new,
            const unsigned short c_cflag)
{
    struct termios tty;
    char *c;

    tcgetattr(mfd, &tty);
    
    tty.c_iflag = IGNBRK | IGNPAR;
    tty.c_oflag = 0;
    tty.c_lflag = 0;
    tty.c_line = 0;
    tty.c_cc[VTIME] = 0;
    tty.c_cc[VMIN] = 1;

    switch (old)
    {
    	case 9600:	tty.c_cflag = c_cflag | B9600; break;
    	case 4800:	tty.c_cflag = c_cflag | B4800; break;
    	case 2400:	tty.c_cflag = c_cflag | B2400; break;
    	case 1200:
	default:	tty.c_cflag = c_cflag | B1200; break;
    }

    tcsetattr(mfd, TCSAFLUSH, &tty);

    switch (new)
    {
    	case 9600:	c = "*q";  tty.c_cflag = c_cflag | B9600; break;
    	case 4800:	c = "*p";  tty.c_cflag = c_cflag | B4800; break;
    	case 2400:	c = "*o";  tty.c_cflag = c_cflag | B2400; break;
    	case 1200:
	default:	c = "*n";  tty.c_cflag = c_cflag | B1200; break;
    }

    write(mfd, c, 2);
    usleep(10000);
    tcsetattr(mfd, TCSAFLUSH, &tty);
}

static int
ms_init()
{
    /* Added O_NDELAY here. */
    if ((mfd = open(mdev, O_RDWR | O_NDELAY)) < 0)
	return -1;

    if (mtype != MOUSE_BUSMOUSE)
    {
	ms_setspeed(9600, mbaud, cflag[mtype]);
	ms_setspeed(4800, mbaud, cflag[mtype]);
	ms_setspeed(2400, mbaud, cflag[mtype]);
	ms_setspeed(1200, mbaud, cflag[mtype]);

	if (mtype == MOUSE_LOGITECH)
	{
	    write(mfd, "S", 1);
	    ms_setspeed(mbaud, mbaud, cflag[MOUSE_MMSERIES]);
	}

	if	(msample <= 0)		write(mfd, "O", 1);
	else if	(msample <= 15)		write(mfd, "J", 1);
	else if	(msample <= 27)		write(mfd, "K", 1);
	else if	(msample <= 42)		write(mfd, "L", 1);
	else if	(msample <= 60)		write(mfd, "R", 1);
	else if	(msample <= 85)		write(mfd, "M", 1);
	else if	(msample <= 125)	write(mfd, "Q", 1);
	else				write(mfd, "N", 1);
    }
    return 0;
}

/* Scooped from X driver. */
static inline void
ms_close() {
    if (mfd == -1)
    	return;
    if (mtype == MOUSE_LOGITECH) {
        write(mfd, "U", 1);
        ms_setspeed(mbaud, 1200, cflag[MOUSE_LOGITECH]);
    }
    close(mfd);
}

#if 0
/* This implementation is very inefficient, but supports 'type 2' operation */
/* (see below) (it may not work correctly though). */

static int
get_ms_event( int wait )
/*
    Changed to process multiple packets.
    wait value:
	0	Process any mouse events, and return status.
	1	Wait for mouse event, then return.
	2	Go fish for a mouse event for a while, and then return status.

	Status indicates whether an event was processed.
	We need method 2 to be able to block virtual console switching
	while reading mouse data for little periods of time, and handle
	console switches in between. It is currently implemented with
	busy-waiting (bad).
*/
{
    unsigned char buf[5];
    char dx, dy;
    int i;
    char but;
    char event_handled = 0;
    int time;

    if (mfd == -1)
	return -1;


    if (wait == 2)
        time = clock();		/* 1/100th of a second */

again:

    if ((wait == 0 || wait == 2) && mfdmode == 1) {
    	/* We don't want to wait, set NDELAY mode. */
    	fcntl(mfd, F_SETFL, O_RDWR | O_NDELAY);
	mfdmode = 0;
    }
    if (wait == 1 && mfdmode == 0) {
    	/* Wait mode, we'll sleep on reads. */
    	fcntl(mfd, F_SETFL, O_RDWR);
    	mfdmode = 1;
    }

    if (read(mfd, &buf[0], 1) != 1)
    	if (wait == 0)
	    return event_handled;
	else {			/* wait == 2 */
	    if (clock() != time)
	        return 0;	/* No event handled. */
	    time = clock();
	    goto again;
	}

restart:

/* find a header packet */
    if ((buf[0] & proto[mtype][0]) != proto[mtype][1])
    	goto again;

#if 0
    if ((wait == 0 || wait == 2) && mfdmode == 0) {
    	/* For the rest of the packet, wait on reads. */
    	/* Otherwise we would never receive a full packet. */
    	fcntl(mfd, F_SETFL, O_RDWR);
    	mfdmode = 1;
    }
#endif

/* read in the rest of the packet */
    for (i = 1; i < proto[mtype][4]; ++i)
    {
	while (read(mfd, &buf[i], 1) != 1);

/* check whether it's a data packet */
	if ((buf[i] & proto[mtype][2]) != proto[mtype][3] || buf[i] == 0x80)
	    goto restart;
    }

/* construct the event */
    switch (mtype)
    {
	case MOUSE_MICROSOFT:		/* Microsoft */
	default:
	    but = ((buf[0] & 0x20) >> 3) | ((buf[0] & 0x10) >> 4);
	    dx = (char)(((buf[0] & 0x03) << 6) | (buf[1] & 0x3F));
	    dy = (char)(((buf[0] & 0x0C) << 4) | (buf[2] & 0x3F));
	    break;
	case MOUSE_MOUSESYSTEMS:	/* Mouse Systems Corp */
	    but = (~buf[0]) & 0x07;
	    dx =    (char)(buf[1]) + (char)(buf[3]);
	    dy = - ((char)(buf[2]) + (char)(buf[4]));
	    break;
	case MOUSE_MMSERIES:		/* MM Series */
	case MOUSE_LOGITECH:		/* Logitech */
	    but = buf[0] & 0x07;
	    dx = (buf[0] & 0x10) ?   buf[1] : - buf[1];
	    dy = (buf[0] & 0x08) ? - buf[2] :   buf[2];
	    break;
	case MOUSE_BUSMOUSE:		/* BusMouse */
	    but = (~buf[0]) & 0x07;
	    dx =   (char)buf[1];
	    dy = - (char)buf[2];
	    break;
    }

    event_handled = 1;
    __mouse_eventhandler(but, dx, dy);

    goto again;
}
#endif


#define MOUSEBUFFERSIZE 256

static int
get_ms_event( int wait )
/*
    Changed to process multiple packets.
    wait value:
	0	Process any mouse events, and return status.
	1	Wait for mouse event, then return.

	Status indicates whether an event was processed.
*/
{
    static unsigned char buf[MOUSEBUFFERSIZE];
    static int nu_bytes = 0;
    char event_handled = 0;
    int bytesread;
    int i;
    char but;
    char dx, dy;
    int j;

    if (mfd == -1)
	return -1;

again:
	
    if (mfdmode == 1) {
    	/* We don't want to wait, set NDELAY mode. */
    	fcntl(mfd, F_SETFL, O_RDONLY | O_NDELAY);
	mfdmode = 0;
    }
 
    bytesread = read(mfd, &buf[nu_bytes], MOUSEBUFFERSIZE - nu_bytes);

    i = 0;

    if (bytesread >= 1)
    	nu_bytes += bytesread;

#ifdef DEBUG
	printf("#bytes in buffer: %d\n", nu_bytes);
#endif

handle_packets:

    /* Handle packets in buffer. */

#ifdef DEBUG
	printf("Bytes left in buffer: %d, packet is %d bytes\n",
		nu_bytes - i, proto[mtype][4]);
#endif

    if (nu_bytes - i < proto[mtype][4])
        /* No full packet available. */
        if (wait == 0 || (wait == 1 && event_handled)) {
            if (i >= nu_bytes) {
           	nu_bytes = 0;
           	i = 0;
            }
            else {
	    	/* Move partial packet to front of buffer. */
	    	for (j = i; j < nu_bytes; j++)
	    		buf[j - i] = buf[j];
	    	nu_bytes -= i;
	    }
            return event_handled;
        }
        else {	/* (wait == 1 && !event_handled) */
    	    if (i >= nu_bytes) {
    	        nu_bytes = 0;
    	        i = 0;
    	    }
    	    /* Wait mode, we'll sleep on reads. */
    	    fcntl(mfd, F_SETFL, O_RDONLY);
    	    mfdmode = 1;
            read(mfd, &buf[nu_bytes], 1);
            nu_bytes++;
            if (nu_bytes - i < proto[mtype][4])
                /* Not a complete packet. */
                goto again;
        }

    /* Check header byte. */
    if ((buf[i] & proto[mtype][0]) != proto[mtype][1]) {
    	/* Not a header byte. */
    	i++;
        goto handle_packets;
    }

    /* Check whether it's a valid data packet. */
    for (j = 1; j < proto[mtype][4]; j++)
        if ((buf[i + j] & proto[mtype][2]) != proto[mtype][3]
        || buf[i + j] == 0x80) {
            i = i + j + 1;
	    goto handle_packets;
	}

    /* Construct the event. */
    switch (mtype) {
	case MOUSE_MICROSOFT:		/* Microsoft */
	default:
	    but = ((buf[i] & 0x20) >> 3) | ((buf[i] & 0x10) >> 4);
	    dx = (char)(((buf[i] & 0x03) << 6) | (buf[i + 1] & 0x3F));
	    dy = (char)(((buf[i] & 0x0C) << 4) | (buf[i + 2] & 0x3F));
	    break;
	case MOUSE_MOUSESYSTEMS:	/* Mouse Systems Corp */
	    but = (~buf[i]) & 0x07;
	    dx =    (char)(buf[i + 1]) + (char)(buf[i + 3]);
	    dy = - ((char)(buf[i + 2]) + (char)(buf[i + 4]));
	    break;
	case MOUSE_MMSERIES:		/* MM Series */
	case MOUSE_LOGITECH:		/* Logitech */
	    but = buf[i] & 0x07;
	    dx = (buf[i] & 0x10) ?   buf[i + 1] : - buf[i + 1];
	    dy = (buf[i] & 0x08) ? - buf[i + 2] :   buf[i + 2];
	    break;
	case MOUSE_BUSMOUSE:		/* BusMouse */
	    but = (~buf[i]) & 0x07;
	    dx =   (char)buf[i + 1];
	    dy = - (char)buf[i + 2];
	    break;
    }

    __mouse_eventhandler(but, dx, dy);

    i += proto[mtype][4];
    event_handled = 1;

    goto handle_packets;
}
