/*
 * 86Box	A hypervisor and IBM PC system emulator that specializes in
 *		running old operating systems and software designed for IBM
 *		PC systems and compatibles from 1981 through fairly recent
 *		system designs based on the PCI bus.
 *
 *		This file is part of the 86Box distribution.
 *
 *		Implementation of Serial Mouse devices.
 *
 * TODO:	Add the Genius Serial Mouse.
 *
 *
 *
 * Author:	Fred N. van Kempen, <decwiz@yahoo.com>
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/timer.h>
#include <86box/serial.h>
#include <86box/mouse.h>


#define SERMOUSE_PORT			0			/* attach to Serial0 */

enum {
    PHASE_IDLE,
    PHASE_ID,
    PHASE_DATA,
    PHASE_STATUS,
    PHASE_DIAGNOSTIC,
    PHASE_FORMAT_AND_REVISION,
    PHASE_COPYRIGHT_STRING,
    PHASE_BUTTONS
};

enum {
    REPORT_PHASE_PREPARE,
    REPORT_PHASE_TRANSMIT
};


typedef struct {
    const char	*name;				/* name of this device */
    int8_t	type,				/* type of this device */
		port;
    uint8_t	flags, but,			/* device flags */
		want_data,
		status, format,
		prompt, on_change,
		id_len, id[255],
		data_len, data[5];
    int		abs_x, abs_y,
		rel_x, rel_y,
		rel_z,
		oldb, lastb;

    int		command_pos, command_phase,
		report_pos, report_phase,
		command_enabled, report_enabled;
    double	transmit_period, report_period;
    pc_timer_t	command_timer, report_timer;

    serial_t	*serial;
} mouse_t;
#define FLAG_INPORT	0x80			/* device is MS InPort */
#define FLAG_3BTN	0x20			/* enable 3-button mode */
#define FLAG_SCALED	0x10			/* enable delta scaling */
#define FLAG_INTR	0x04			/* dev can send interrupts */
#define FLAG_FROZEN	0x02			/* do not update counters */
#define FLAG_ENABLED	0x01			/* dev is enabled for use */


#ifdef ENABLE_MOUSE_SERIAL_LOG
int mouse_serial_do_log = ENABLE_MOUSE_SERIAL_LOG;


static void
mouse_serial_log(const char *fmt, ...)
{
    va_list ap;

    if (mouse_serial_do_log) {
	va_start(ap, fmt);
	pclog_ex(fmt, ap);
	va_end(ap);
    }
}
#else
#define mouse_serial_log(fmt, ...)
#endif


static void
sermouse_timer_on(mouse_t *dev, double period, int report)
{
    pc_timer_t *timer;
    int *enabled;

    if (report) {
	timer = &dev->report_timer;
	enabled = &dev->report_enabled;
    } else {
	timer = &dev->command_timer;
	enabled = &dev->command_enabled;
    }

    timer_on_auto(timer, period);

    *enabled = 1;
}


static double
sermouse_transmit_period(mouse_t *dev, int bps, int rps)
{
    double dbps = (double) bps;
    double temp = 0.0;
    int word_len;

    switch (dev->format) {
	case 0:
	case 1:		/* Mouse Systems and Three Byte Packed formats: 8 data, no parity, 2 stop, 1 start */
		word_len = 11;
		break;
	case 2:		/* Hexadecimal format - 8 data, no parity, 1 stop, 1 start - number of stop bits is a guess because
			   it is not documented anywhere. */
		word_len = 10;
		break;
	case 3:
	case 6:		/* Bit Pad One formats: 7 data, even parity, 2 stop, 1 start */
		word_len = 11;
		break;
	case 5:		/* MM Series format: 8 data, odd parity, 1 stop, 1 start */
		word_len = 11;
		break;
	default:
	case 7:		/* Microsoft-compatible format: 7 data, no parity, 1 stop, 1 start */
		word_len = 9;
		break;
    }

    if (rps == -1)
	temp = (double) word_len;
    else {
	temp = (double) rps;
	temp = (9600.0 - (temp * 33.0));
	temp /= rps;
    }
    temp = (1000000.0 / dbps) * temp;

    return temp;
}


/* Callback from serial driver: RTS was toggled. */
static void
sermouse_callback(struct serial_s *serial, void *priv)
{
    mouse_t *dev = (mouse_t *)priv;

    /* Start a timer to wake us up in a little while. */
    dev->command_pos = 0;
    dev->command_phase = PHASE_ID;
    if (dev->id[0] != 'H')
	dev->format = 7;
    dev->transmit_period = sermouse_transmit_period(dev, 1200, -1);
    timer_stop(&dev->command_timer);
#ifdef USE_NEW_DYNAREC
    sermouse_timer_on(dev, cpu_use_dynarec ? 5000.0 : dev->transmit_period, 0);
#else
    sermouse_timer_on(dev, dev->transmit_period, 0);
#endif
}


static uint8_t
sermouse_data_msystems(mouse_t *dev, int x, int y, int b)
{
    dev->data[0] = 0x80;
    dev->data[0] |= (b & 0x01) ? 0x00 : 0x04;	/* left button */
    dev->data[0] |= (b & 0x02) ? 0x00 : 0x01;	/* middle button */
    dev->data[0] |= (b & 0x04) ? 0x00 : 0x02;	/* right button */
    dev->data[1] = x;
    dev->data[2] = -y;
    dev->data[3] = x;				/* same as byte 1 */
    dev->data[4] = -y;				/* same as byte 2 */

    return 5;
}


static uint8_t
sermouse_data_3bp(mouse_t *dev, int x, int y, int b)
{
    dev->data[0] |= (b & 0x01) ? 0x00 : 0x04;	/* left button */
    dev->data[0] |= (b & 0x04) ? 0x00 : 0x02;	/* middle button */
    dev->data[0] |= (b & 0x02) ? 0x00 : 0x01;	/* right button */
    dev->data[1] = x;
    dev->data[2] = -y;

    return 3;
}


static uint8_t
sermouse_data_mmseries(mouse_t *dev, int x, int y, int b)
{
    if (x < -127)
	x = -127;
    if (y < -127)
	y = -127;

    dev->data[0] = 0x80;
    if (x >= 0)
	dev->data[0] |= 0x10;
    if (y < 0)
	dev->data[0] |= 0x08;
    dev->data[0] |= (b & 0x01) ? 0x04 : 0x00;	/* left button */
    dev->data[0] |= (b & 0x04) ? 0x02 : 0x00;	/* middle button */
    dev->data[0] |= (b & 0x02) ? 0x01 : 0x00;	/* right button */
    dev->data[1] = abs(x);
    dev->data[2] = abs(y);

    return 3;
}


static uint8_t
sermouse_data_bp1(mouse_t *dev, int x, int y, int b)
{
    dev->data[0] = 0x80;
    dev->data[0] |= (b & 0x01) ? 0x10 : 0x00;	/* left button */
    dev->data[0] |= (b & 0x04) ? 0x08 : 0x00;	/* middle button */
    dev->data[0] |= (b & 0x02) ? 0x04 : 0x00;	/* right button */
    dev->data[1] = (x & 0x3f);
    dev->data[2] = (x >> 6);
    dev->data[3] = (y & 0x3f);
    dev->data[4] = (y >> 6);

    return 5;
}


static uint8_t
sermouse_data_ms(mouse_t *dev, int x, int y, int z, int b)
{
    uint8_t len;

    dev->data[0] = 0x40;
    dev->data[0] |= (((y >> 6) & 0x03) << 2);
    dev->data[0] |= ((x >> 6) & 0x03);
    if (b & 0x01)
	dev->data[0] |= 0x20;
    if (b & 0x02)
	dev->data[0] |= 0x10;
    dev->data[1] = x & 0x3F;
    dev->data[2] = y & 0x3F;
    if (dev->but == 3) {
	len = 3;
	if (dev->type == MOUSE_TYPE_LT3BUTTON) {
		if (b & 0x04) {
			dev->data[3] = 0x20;
			len++;
		}
	} else {
		if ((b ^ dev->oldb) & 0x04) {
			/* Microsoft 3-button mice send a fourth byte of 0x00 when the middle button
			   has changed. */
			dev->data[3] = 0x00;
			len++;
		}
	}
    } else if (dev->but == 4) {
	len = 4;
	dev->data[3] = z & 0x0F;
	if (b & 0x04)
		dev->data[3] |= 0x10;
    } else
	len = 3;

    return len;
}


static uint8_t
sermouse_data_hex(mouse_t *dev, int x, int y, int b)
{
    char ret[6] = { 0, 0, 0, 0, 0, 0 };
    uint8_t i, but = 0x00;

    but |= (b & 0x01) ? 0x04 : 0x00;	/* left button */
    but |= (b & 0x04) ? 0x02 : 0x00;	/* middle button */
    but |= (b & 0x02) ? 0x01 : 0x00;	/* right button */

    sprintf(ret, "%02X%02X%01X", (int8_t) y, (int8_t) x, but & 0x0f);

    for (i = 0; i < 5; i++)
	dev->data[i] = ret[4 - i];

    return 5;
}


static void
sermouse_report(int x, int y, int z, int b, mouse_t *dev)
{
    int len = 0;

    memset(dev->data, 0, 5);

    /* If the mouse is 2-button, ignore the middle button. */
    if (dev->but == 2)
	b &= ~0x04;

    switch (dev->format) {
	case 0:
		len = sermouse_data_msystems(dev, x, y, b);
		break;
	case 1:
		len = sermouse_data_3bp(dev, x, y, b);
		break;
	case 2:
		len = sermouse_data_hex(dev, x, y, b);
		break;
	case 3:	/* Relative */
		len = sermouse_data_bp1(dev, x, y, b);
		break;
	case 5:
		len = sermouse_data_mmseries(dev, x, y, b);
		break;
	case 6:	/* Absolute */
		len = sermouse_data_bp1(dev, dev->abs_x, dev->abs_y, b);
		break;
	case 7:
		len = sermouse_data_ms(dev, x, y, z, b);
		break;
    }

    dev->data_len = len;
}


static void
sermouse_command_phase_idle(mouse_t *dev)
{
    dev->command_pos = 0;
    dev->command_phase = PHASE_IDLE;
    dev->command_enabled = 0;
}


static void
sermouse_command_pos_check(mouse_t *dev, int len)
{
    if (++dev->command_pos == len)
	sermouse_command_phase_idle(dev);
    else
	timer_on_auto(&dev->command_timer, dev->transmit_period);
}


static uint8_t
sermouse_last_button_status(mouse_t *dev)
{
    uint8_t ret = 0x00;

    if (dev->oldb & 0x01)
	ret |= 0x04;
    if (dev->oldb & 0x02)
	ret |= 0x02;
    if (dev->oldb & 0x04)
	ret |= 0x01;

    return ret;
}


static void
sermouse_update_delta(mouse_t *dev, int *local, int *global)
{
    int min, max;

    if (dev->format == 3) {
	min = -2048;
	max = 2047;
    } else {
	min = -128;
	max = 127;
    }

    if (*global > max) {
	*local = max;
	*global -= max;
    } else if (*global < min) {
	*local = min;
	*global += -min;
    } else {
	*local = *global;
	*global = 0;
    }
}


static uint8_t
sermouse_update_data(mouse_t *dev)
{
    uint8_t ret = 0;
    int delta_x, delta_y, delta_z;

    /* Update the deltas and the delays. */
    sermouse_update_delta(dev, &delta_x, &dev->rel_x);
    sermouse_update_delta(dev, &delta_y, &dev->rel_y);
    sermouse_update_delta(dev, &delta_z, &dev->rel_z);

    sermouse_report(delta_x, delta_y, delta_z, dev->oldb, dev);

    mouse_serial_log("delta_x = %i, delta_y = %i, delta_z = %i, dev->oldb = %02X\n",
		     delta_x, delta_y, delta_z, dev->oldb);

    if (delta_x || delta_y || delta_z || (dev->oldb != dev->lastb) || !dev->on_change)
	ret = 1;

    dev->lastb = dev->oldb;

    mouse_serial_log("sermouse_update_data(): ret = %i\n", ret);

    return ret;
}


static double
sermouse_report_period(mouse_t *dev)
{
    if (dev->report_period == 0)
	return dev->transmit_period;
    else
	return dev->report_period;
}


static void
sermouse_report_prepare(mouse_t *dev)
{
    if (sermouse_update_data(dev)) {
	/* Start sending data. */
	dev->report_phase = REPORT_PHASE_TRANSMIT;
	dev->report_pos = 0;
	sermouse_timer_on(dev, dev->transmit_period, 1);
    } else {
	dev->report_phase = REPORT_PHASE_PREPARE;
	sermouse_timer_on(dev, sermouse_report_period(dev), 1);
    }
}


static void
sermouse_report_timer(void *priv)
{
    mouse_t *dev = (mouse_t *)priv;

    if (dev->report_phase == REPORT_PHASE_PREPARE)
	sermouse_report_prepare(dev);
    else {
	/* If using the Mouse Systems format, update data because
	   the last two bytes are the X and Y delta since bytes 1
	   and 2 were transmitted. */
	if (!dev->format && (dev->report_pos == 3))
		sermouse_update_data(dev);
	serial_write_fifo(dev->serial, dev->data[dev->report_pos]);
	if (++dev->report_pos == dev->data_len) {
		if (!dev->report_enabled)
			sermouse_report_prepare(dev);
		else {
			sermouse_timer_on(dev, sermouse_report_period(dev), 1);
			dev->report_phase = REPORT_PHASE_PREPARE;
		}
	} else
		sermouse_timer_on(dev, dev->transmit_period, 1);
    }
}


/* Callback timer expired, now send our "mouse ID" to the serial port. */
static void
sermouse_command_timer(void *priv)
{
    mouse_t *dev = (mouse_t *)priv;

    switch (dev->command_phase) {
	case PHASE_ID:
		serial_write_fifo(dev->serial, dev->id[dev->command_pos]);
		sermouse_command_pos_check(dev, dev->id_len);
		if ((dev->command_phase == PHASE_IDLE) && (dev->type != MOUSE_TYPE_MSYSTEMS)) {
			/* This resets back to Microsoft-compatible mode. */
			dev->report_phase = REPORT_PHASE_PREPARE;
			sermouse_report_timer((void *) dev);
		}
		break;
	case PHASE_DATA:
		serial_write_fifo(dev->serial, dev->data[dev->command_pos]);
		sermouse_command_pos_check(dev, dev->data_len);
		break;
	case PHASE_STATUS:
		serial_write_fifo(dev->serial, dev->status);
		sermouse_command_phase_idle(dev);
		break;
	case PHASE_DIAGNOSTIC:
		if (dev->command_pos)
			serial_write_fifo(dev->serial, 0x00);
		else
			serial_write_fifo(dev->serial, sermouse_last_button_status(dev));
		sermouse_command_pos_check(dev, 3);
		break;
	case PHASE_FORMAT_AND_REVISION:
		serial_write_fifo(dev->serial, 0x10 | (dev->format << 1));
		sermouse_command_phase_idle(dev);
		break;
	case PHASE_BUTTONS:
		serial_write_fifo(dev->serial, dev->but);
		sermouse_command_phase_idle(dev);
		break;
	default:
		sermouse_command_phase_idle(dev);
		break;
    }
}


static int
sermouse_poll(int x, int y, int z, int b, void *priv)
{
    mouse_t *dev = (mouse_t *)priv;

    if (!x && !y && !z && (b == dev->oldb)) {
	dev->oldb = b;
	return(1);
    }

    dev->oldb = b;
    dev->abs_x += x;
    dev->abs_y += y;
    if (dev->abs_x < 0)
	dev->abs_x = 0;
    if (dev->abs_x > 4095)
	dev->abs_x = 4095;
    if (dev->abs_y < 0)
	dev->abs_y = 0;
    if (dev->abs_y > 4095)
	dev->abs_y = 4095;

    if (dev->format == 3) {
	if (x > 2047) x = 2047;
	if (y > 2047) y = 2047;
	if (x <- 2048) x = -2048;
	if (y <- 2048) y = -2048;
    } else {
	if (x > 127) x = 127;
	if (y > 127) y = 127;
	if (x <- 128) x = -128;
	if (y <- 128) y = -128;
    }

    dev->rel_x += x;
    dev->rel_y += y;
    dev->rel_z += z;

    return(0);
}


static void
ltsermouse_prompt_mode(mouse_t *dev, int prompt)
{
    dev->prompt = prompt;
    dev->status &= 0xBF;
    if (prompt)
	dev->status |= 0x40;
}


static void
ltsermouse_command_phase(mouse_t *dev, int phase)
{
    dev->command_pos = 0;
    dev->command_phase = phase;
    timer_stop(&dev->command_timer);
    sermouse_timer_on(dev, dev->transmit_period, 0);
}


static void
ltsermouse_set_report_period(mouse_t *dev, int rps)
{
    dev->report_period = sermouse_transmit_period(dev, 9600, rps);
    timer_stop(&dev->report_timer);
    sermouse_timer_on(dev, dev->report_period, 1);
    ltsermouse_prompt_mode(dev, 0);
    dev->report_phase = REPORT_PHASE_PREPARE;
}


static void
ltsermouse_write(struct serial_s *serial, void *priv, uint8_t data)
{
    mouse_t *dev = (mouse_t *)priv;

    /* Stop reporting when we're processing a command. */
    dev->report_phase = REPORT_PHASE_PREPARE;

    if (dev->want_data)  switch (dev->want_data) {
	case 0x2A:
		dev->data_len--;
		dev->want_data = 0;
		switch (data) {
			default:
				mouse_serial_log("Serial mouse: Invalid period %02X, using 1200 bps\n", data);
				/*FALLTHROUGH*/
			case 0x6E:
				dev->transmit_period = sermouse_transmit_period(dev, 1200, -1);
				break;
			case 0x6F:
				dev->transmit_period = sermouse_transmit_period(dev, 2400, -1);
				break;
			case 0x70:
				dev->transmit_period = sermouse_transmit_period(dev, 4800, -1);
				break;
			case 0x71:
				dev->transmit_period = sermouse_transmit_period(dev, 9600, -1);
				break;
		}
		break;
    } else  switch (data) {
	case 0x2A:
		dev->want_data = data;
		dev->data_len = 1;
		break;
	case 0x44:	/* Set prompt mode */
		ltsermouse_prompt_mode(dev, 1);
		break;
	case 0x50:
		if (!dev->prompt)
			ltsermouse_prompt_mode(dev, 1);
		sermouse_update_data(dev);
		ltsermouse_command_phase(dev, PHASE_DATA);
		break;
	case 0x73:	/* Status */
		ltsermouse_command_phase(dev, PHASE_STATUS);
		break;
	case 0x4A:	/* Report Rate Selection commands */
		ltsermouse_set_report_period(dev, 10);
		break;
	case 0x4B:
		ltsermouse_set_report_period(dev, 20);
		break;
	case 0x4C:
		ltsermouse_set_report_period(dev, 35);
		break;
	case 0x52:
		ltsermouse_set_report_period(dev, 50);
		break;
	case 0x4D:
		ltsermouse_set_report_period(dev, 70);
		break;
	case 0x51:
		ltsermouse_set_report_period(dev, 100);
		break;
	case 0x4E:
		ltsermouse_set_report_period(dev, 150);
		break;
	case 0x4F:
		ltsermouse_prompt_mode(dev, 0);
		dev->report_period = 0;
		timer_stop(&dev->report_timer);
		dev->report_phase = REPORT_PHASE_PREPARE;
		sermouse_report_timer((void *) dev);
		break;
	case 0x41:
		dev->format = 6;	/* Aboslute Bit Pad One Format */
		dev->abs_x = dev->abs_y = 0;
		break;
	case 0x42:
		dev->format = 3;	/* Relative Bit Pad One Format */
		break;
	case 0x53:
		dev->format = 5;	/* MM Series Format */
		break;
	case 0x54:
		dev->format = 1;	/* Three Byte Packed Binary Format */
		break;
	case 0x55:	/* This is the Mouse Systems-compatible format */
		dev->format = 0;	/* Five Byte Packed Binary Format */
		break;
	case 0x56:
		dev->format = 7;	/* Microsoft Compatible Format */
		break;
	case 0x57:
		dev->format = 2;	/* Hexadecimal Format */
		break;
	case 0x05:
		ltsermouse_command_phase(dev, PHASE_DIAGNOSTIC);
		break;
	case 0x66:
		ltsermouse_command_phase(dev, PHASE_FORMAT_AND_REVISION);
		break;
	case 0x6B:
		ltsermouse_command_phase(dev, PHASE_BUTTONS);
		break;
    }
}


static void
sermouse_speed_changed(void *priv)
{
    mouse_t *dev = (mouse_t *)priv;

    if (dev->report_enabled) {
	timer_stop(&dev->report_timer);
	if (dev->report_phase == REPORT_PHASE_TRANSMIT)	
		sermouse_timer_on(dev, dev->transmit_period, 1);
	else
		sermouse_timer_on(dev, sermouse_report_period(dev), 1);
    }

    if (dev->command_enabled) {
	timer_stop(&dev->command_timer);
	sermouse_timer_on(dev, dev->transmit_period, 0);
    }
}


static void
sermouse_close(void *priv)
{
    mouse_t *dev = (mouse_t *)priv;

    /* Detach serial port from the mouse. */
    if (dev && dev->serial && dev->serial->sd)
	memset(dev->serial->sd, 0, sizeof(serial_device_t));

    free(dev);
}


/* Initialize the device for use by the user. */
static void *
sermouse_init(const device_t *info)
{
    mouse_t *dev;

    dev = (mouse_t *)malloc(sizeof(mouse_t));
    memset(dev, 0x00, sizeof(mouse_t));
    dev->name = info->name;
    dev->but = device_get_config_int("buttons");
    if (dev->but > 2)
	dev->flags |= FLAG_3BTN;

    if (info->local == MOUSE_TYPE_MSYSTEMS) {
	dev->on_change = 1;
	dev->format = 0;
	dev->type = info->local;
	dev->id_len = 1;
	dev->id[0] = 'H';
    } else {
	dev->on_change = !info->local;
	dev->format = 7;
	dev->status = 0x0f;
	dev->id_len = 1;
	dev->id[0] = 'M';
	switch(dev->but) {
		case 2:
		default:
			dev->type = info->local ? MOUSE_TYPE_LOGITECH : MOUSE_TYPE_MICROSOFT;
			break;
		case 3:
			dev->type = info->local ? MOUSE_TYPE_LT3BUTTON : MOUSE_TYPE_MS3BUTTON;
			dev->id_len = 2;
			dev->id[1] = '3';
			break;
		case 4:
			dev->type = MOUSE_TYPE_MSWHEEL;
			dev->id_len = 6;
			dev->id[1] = 'Z';
			dev->id[2] = '@';
			break;
	}
    }

    dev->transmit_period = sermouse_transmit_period(dev, 1200, -1);

    /* Default: Continuous reporting = no delay between reports. */
    dev->report_phase = REPORT_PHASE_PREPARE;
    dev->report_period = 0;

    /* Default: Doing nothing - command transmit timer deactivated. */
    dev->command_phase = PHASE_IDLE;

    dev->port = device_get_config_int("port");

    /* Attach a serial port to the mouse. */
    if (info->local)
	dev->serial = serial_attach(dev->port, sermouse_callback, ltsermouse_write, dev);
    else
	dev->serial = serial_attach(dev->port, sermouse_callback, NULL, dev);

    mouse_serial_log("%s: port=COM%d\n", dev->name, dev->port + 1);

    timer_add(&dev->report_timer, sermouse_report_timer, dev, 0);
    timer_add(&dev->command_timer, sermouse_command_timer, dev, 0);

    if (info->local == MOUSE_TYPE_MSYSTEMS) {
	sermouse_timer_on(dev, dev->transmit_period, 1);
	dev->report_enabled = 1;
    }

    /* Tell them how many buttons we have. */
    mouse_set_buttons((dev->flags & FLAG_3BTN) ? 3 : 2);

    /* Return our private data to the I/O layer. */
    return(dev);
}


static const device_config_t mssermouse_config[] = {
    {
	"port", "Serial Port", CONFIG_SELECTION, "", 0, "", { 0 }, {
		{
			"COM1", 0
		},
		{
			"COM2", 1
		},
		{
			""
		}
	}
    },
    {
	"buttons", "Buttons", CONFIG_SELECTION, "", 2, "", { 0 }, {
		{
			"Two", 2
		},
		{
			"Three", 3
		},
		{
			"Wheel", 4
		},
		{
			""
		}
	}
    },
    {
	"", "", -1
    }
};


static const device_config_t ltsermouse_config[] = {
    {
	"port", "Serial Port", CONFIG_SELECTION, "", 0, "", { 0 }, {
		{
			"COM1", 0
		},
		{
			"COM2", 1
		},
		{
			""
		}
	}
    },
    {
	"buttons", "Buttons", CONFIG_SELECTION, "", 2, "", { 0 }, {
		{
			"Two", 2
		},
		{
			"Three", 3
		},
		{
			""
		}
	}
    },
    {
	"", "", -1
    }
};


const device_t mouse_mssystems_device = {
    "Mouse Systems Serial Mouse",
    "mssystems",
    0,
    MOUSE_TYPE_MSYSTEMS,
    sermouse_init, sermouse_close, NULL,
    { .poll = sermouse_poll }, sermouse_speed_changed, NULL,
    mssermouse_config
};

const device_t mouse_msserial_device = {
    "Microsoft Serial Mouse",
    "msserial",
    0,
    0,
    sermouse_init, sermouse_close, NULL,
    { .poll = sermouse_poll }, sermouse_speed_changed, NULL,
    mssermouse_config
};

const device_t mouse_ltserial_device = {
    "Logitech Serial Mouse",
    "ltserial",
    0,
    1,
    sermouse_init, sermouse_close, NULL,
    { .poll = sermouse_poll }, sermouse_speed_changed, NULL,
    ltsermouse_config
};
