/*
 * OptMouse.c
 *
 * Serial Port Optical Mouse Driver
 *
 * (c) Copyright 1989 J. Edward Hanway
 * This code may be freely redistributed for non-commercial purposes. There
 * is NO WARRANTY on this program. The author assumes no responsibility for
 * any damages resulting from use of this program. You know the drill.
 *
 * Language: Lattice C 5.02
 *
 * Revision History:
 *
 * Version 1.0	27 May 1989
 *	Supports Mouse Systems M2/M3 serial optical mouse
 * Version 1.1  3 June 1989
 *	Added support for other device/unit combinations besides serial.device
 *	(e.g. siosbx.device) *** NOTE: This feature has not been tested. ***
 */

#define VERSION "1.1"

#include <exec/types.h>
#include <devices/serial.h>
#include <devices/input.h>
#include <devices/inputevent.h>
#include <devices/keyboard.h>
#include <libraries/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <string.h>
#include <stdlib.h>

#define SAY(x) \
{if(_Backstdout) {Write(_Backstdout, (x), strlen(x)); Close(_Backstdout);}}

void MemCleanup() {}

/* This stuff is for linking with cback.o (for "load and stay resident" code) */

LONG	_stack = 4000;		/* stack size */
char	*_procname = "OptMouse";/* process name */
LONG	_priority = 20;		/* priority */
LONG	_BackGroundIO = 1;	/* requires I/O */
extern BPTR _Backstdout;

static char		*ser_portname = "OptMouse";
	
/* default device/unit */
static char		*device = SERIALNAME;
static int		unit = 0;


static struct dev {
	struct MsgPort		*port;
	struct IORequest	*iob;
	BOOL			open;
}	ser = {NULL, NULL, FALSE},
	in = {NULL, NULL, FALSE},
	time = {NULL, NULL, FALSE},
	key = {NULL, NULL, FALSE};
	
/* shorthand */
#define SER_IOB		((struct IOExtSer *)(ser.iob))
#define IN_IOB		((struct IOStdReq *)(in.iob))
#define TIME_IOB	((struct timerequest *)(time.iob))
#define KEY_IOB		((struct IOStdReq *)(key.iob))

static signed char	b;			/* byte read from mouse */

static struct button {
	BOOL left, middle, right;
}	button, last_button = { FALSE, FALSE, FALSE };

static struct InputEvent event = {
	NULL,				/* NextEvent */
	IECLASS_RAWMOUSE,		/* Class */
	NULL,				/* SubClass */
	NULL,				/* Code, filled in later */
	NULL,				/* Qualifier, filled in later */
	{ NULL, NULL },			/* Position, filled in later */
	{ 0L, 0L }			/* TimeStamp */	
};

/* Matrix for reading key states. All we care about are the 
 * shift/ctrl/alt/amiga keys, which are conveniently qrouped in
 * keymatrix[12] in the same order as required for the qualifier
 *
 * The following number, which is the ONLY read length that works for
 * KBD_READMATRIX, was documented NOWHERE! I had to find it by trial and error.
 */
#define GODDAMN_KEY_MATRIX_READ_LENGTH	13

static UBYTE keymatrix[16];

void close_dev(struct dev *dev)
{
	if(dev->open) {
		AbortIO(dev->iob);
		CloseDevice(dev->iob);
		dev->open = FALSE;
	}
	if(dev->iob) {
		DeleteExtIO(dev->iob);
		dev->iob = NULL;
	}
	if(dev->port) {
		DeletePort(dev->port);
		dev->port = NULL;
	}
}

void die(void)
{
	close_dev(&time);
	close_dev(&in);
	close_dev(&ser);
	close_dev(&key);
	_exit(0);
}

void open_dev(struct dev *dev, char *portname, char *devname, int unit, int size)
{ 
	if(!(dev->port = CreatePort(portname, 0)) ||
	   !(dev->iob = CreateExtIO(dev->port, size)) ||
	   !(dev->open = !OpenDevice(devname, unit, dev->iob, 0L))) {
		SAY("Device error\n");
		die();
	}
}

/* my_DoIO does everything that DoIO does (I hope) except it allows for a break
 * signal.
 */
void my_DoIO(struct IORequest *iob)
{
	register LONGBITS signals, sigbit;

/* Lattice 5.02 doesn't seem to provide a register-parameter entry to BeginIO,
 * so here's a hack that avoids an annoying 3 line assembly language program.
 * Just don't try to call BeginIO on anything but iob->io_Device.
 */
#pragma libcall iob->io_Device BeginIO 1e 901

	iob->io_Flags |= IOF_QUICK;
	BeginIO(iob);
	if(!(iob->io_Flags & IOF_QUICK)) {
		signals = Wait((sigbit = (1L << 
				iob->io_Message.mn_ReplyPort->mp_SigBit)) |
			       SIGBREAKF_CTRL_C);
		if(signals & sigbit)
			GetMsg(iob->io_Message.mn_ReplyPort);
		if(signals & SIGBREAKF_CTRL_C)
			die();
	}
}

#undef isspace

int isspace(register char c)
{
	return ((c == ' ') || (c == '\t') || (c == '\n') || (c == '\r'));
}

char *skiparg(register char *s)
{
	while(*s && !isspace(*s))
		s++;
	return s;
}

char *skipwhite(register char *s)
{
	while(*s && isspace(*s))
		s++;
	return s;
}

void _tinymain(char *s)
{
	UWORD		state = 0;
	WORD		dx, dy;
	BOOL		mid_is_shift = 0;
	BOOL		kill = 0;

	/* I'm playing it fast and loose with parsing here */
	s = skiparg(s);
	s = skipwhite(s);
	while(*s == '-') {
		switch(*(s+1)) {
		    case 'k':
		    case 'K':
			kill++;
			break;
		    case 'm':
		    case 'M':
			mid_is_shift++;
			break;
		}
		s = skiparg(s);
		s = skipwhite(s);
	}

	/* device name */
	if(*s) {
		device = s;
		s = skiparg(s);
		if(*s) *s++ = '\0';
		s = skipwhite(s);
	}

	/* unit */
	if(*s) {
		(void) stcd_i(s, &unit);
	}

	if(FindPort(ser_portname)) {
		if(kill) {
			Signal(FindTask(_procname),SIGBREAKF_CTRL_C);
			SAY("Killed\n");
		} else
			SAY("Already installed\n");
		die();
	}

	open_dev(&ser, ser_portname, device, unit, sizeof(struct IOExtSer));
	open_dev(&in, NULL, "input.device", 0L, sizeof(struct IOStdReq));
	open_dev(&time, NULL, TIMERNAME, UNIT_MICROHZ, sizeof(struct timerequest));
	open_dev(&key, NULL, "keyboard.device", 0L, sizeof(struct IOStdReq));

	SAY("OptMouse " VERSION " \xA9 Copyright 1989 J. Edward Hanway\n");

	/* Set serial port parameters:
	 * 1200 baud, 8 bits, no parity, 1 stop bit, no flow control
	 */
	SER_IOB->IOSer.io_Command = SDCMD_SETPARAMS;
	SER_IOB->io_Baud = 1200;
	SER_IOB->io_ReadLen = 8;
	SER_IOB->io_StopBits = 1;
	SER_IOB->io_SerFlags = SERF_XDISABLED | SERF_RAD_BOOGIE;
	my_DoIO(ser.iob);

	/* Since each IORequest will be used for only one type of command,
	 * lots of invariant fields can be filled in here
	 */
	KEY_IOB->io_Command = KBD_READMATRIX;
	KEY_IOB->io_Data = (APTR) keymatrix;
	KEY_IOB->io_Length = GODDAMN_KEY_MATRIX_READ_LENGTH;

	TIME_IOB->tr_node.io_Command = TR_GETSYSTIME;

	IN_IOB->io_Command = IND_WRITEEVENT;
	IN_IOB->io_Flags = 0;
	IN_IOB->io_Length = sizeof(struct InputEvent);
	IN_IOB->io_Data = (APTR) &event;

	SER_IOB->IOSer.io_Command = CMD_READ;
	SER_IOB->IOSer.io_Length = 1;
	SER_IOB->IOSer.io_Data = (APTR) &b;

	while(TRUE) {
		my_DoIO(ser.iob);
		switch(state) {
		    case 0:
			if((b & 0xf8) == 0x80) {
				button.right  = !(b & (1 << 0));
				button.middle = !(b & (1 << 1));
				button.left   = !(b & (1 << 2));
				state++;
			}
			break;
		    case 1:
			dx = b;
			state++;
			break;
		    case 2:
			dy = b;
			state++;
			break;
		    case 3:
			dx += b;
			state++;
			break;
		    case 4:
			dy += b;

			/* Now compose the events to be sent to input.device.
			 * Currently, 0-4 events (movement, up to 3 button
			 * state changes) are sent.

			/* Valid time stamp is required for double-clicking
			 * to work properly.
			 */
			my_DoIO(time.iob);
			event.ie_TimeStamp = TIME_IOB->tr_time;

			/* Shift key status is needed for shift-click support */
			my_DoIO(key.iob);

			event.ie_X = dx;
			event.ie_Y = -dy;
			event.ie_Code = IECODE_NOBUTTON;
			event.ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE |
			(button.right ? IEQUALIFIER_RBUTTON : 0) |
			((button.middle && !mid_is_shift) ? IEQUALIFIER_MIDBUTTON : 0) |
			(button.left ? IEQUALIFIER_LEFTBUTTON : 0) | 
			keymatrix[12] |
			((button.middle && mid_is_shift) ? 
			 (IEQUALIFIER_LEFTBUTTON | IEQUALIFIER_LSHIFT) :
			 0);

			if(dx || dy)
				my_DoIO(in.iob);

			event.ie_X = event.ie_Y = 0;

			if(button.left ^ last_button.left) {
				event.ie_Code = button.left ? IECODE_LBUTTON :
						IECODE_LBUTTON | IECODE_UP_PREFIX;
				my_DoIO(in.iob);
			}

			if(button.middle ^ last_button.middle) {
				event.ie_Code = mid_is_shift ? 
						(button.middle ? IECODE_LBUTTON :
						 IECODE_LBUTTON | IECODE_UP_PREFIX) :
						(button.middle ? IECODE_MBUTTON : 
						 IECODE_MBUTTON | IECODE_UP_PREFIX);
				my_DoIO(in.iob);
			}

			if(button.right ^ last_button.right) {
				event.ie_Code = button.right ? IECODE_RBUTTON : 
						IECODE_RBUTTON | IECODE_UP_PREFIX;
				my_DoIO(in.iob);
			}

			last_button = button;
			state = 0;
			break;
		}
	}
}
