mouse.c   [plain text]


/*
 * Copyright © 2001 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

#ifdef HAVE_CONFIG_H
#include <kdrive-config.h>
#endif
#define NEED_EVENTS
#include <errno.h>
#include <termios.h>
#include <X11/X.h>
#include <X11/Xproto.h>
#include <X11/Xpoll.h>
#include "inputstr.h"
#include "scrnintstr.h"
#include "kdrive.h"

#undef DEBUG
#undef DEBUG_BYTES
#define KBUFIO_SIZE 256
#define MOUSE_TIMEOUT	100

typedef struct _kbufio {
    int		    fd;
    unsigned char   buf[KBUFIO_SIZE];
    int		    avail;
    int		    used;
} Kbufio;

static Bool
MouseWaitForReadable (int fd, int timeout)
{
    fd_set	    set;
    struct timeval  tv, *tp;
    int		    n;
    CARD32	    done;

    done = GetTimeInMillis () + timeout;
    for (;;)
    {
	FD_ZERO (&set);
	FD_SET (fd, &set);
	if (timeout == -1)
	    tp = 0;
	else
	{
	    tv.tv_sec = timeout / 1000;
	    tv.tv_usec = (timeout % 1000) * 1000;
	    tp = &tv;
	}
	n = select (fd + 1, &set, 0, 0, tp);
	if (n > 0)
	    return TRUE;
	if (n < 0 && (errno == EAGAIN || errno == EINTR))
	{
	    timeout = (int) (done - GetTimeInMillis ());
	    if (timeout > 0)
		continue;
	}
	break;
    }
    return FALSE;
}

static int
MouseReadByte (Kbufio *b, int timeout)
{
    int	n;
    if (b->avail <= b->used)
    {
	if (timeout && !MouseWaitForReadable (b->fd, timeout))
	{
#ifdef DEBUG_BYTES
	    ErrorF ("\tTimeout %d\n", timeout);
#endif
	    return -1;
	}
	n = read (b->fd, b->buf, KBUFIO_SIZE);
	if (n <= 0)
	    return -1;
        b->avail = n;
        b->used = 0;
    }
#ifdef DEBUG_BYTES
    ErrorF ("\tget %02x\n", b->buf[b->used]);
#endif
    return b->buf[b->used++];
}

#if NOTUSED
static int
MouseFlush (Kbufio *b, char *buf, int size)
{
    CARD32  now = GetTimeInMillis ();
    CARD32  done = now + 100;
    int	    c;
    int	    n = 0;
    
    while ((c = MouseReadByte (b, done - now)) != -1)
    {
	if (buf)
	{
	    if (n == size)
	    {
		memmove (buf, buf + 1, size - 1);
		n--;
	    }
	    buf[n++] = c;
	}
	now = GetTimeInMillis ();
	if ((INT32) (now - done) >= 0)
	    break;
    }
    return n;
}

static int
MousePeekByte (Kbufio *b, int timeout)
{
    int	    c;

    c = MouseReadByte (b, timeout);
    if (c != -1)
	--b->used;
    return c;
}
#endif /* NOTUSED */

static Bool
MouseWaitForWritable (int fd, int timeout)
{
    fd_set	    set;
    struct timeval  tv, *tp;
    int		    n;

    FD_ZERO (&set);
    FD_SET (fd, &set);
    if (timeout == -1)
	tp = 0;
    else
    {
	tv.tv_sec = timeout / 1000;
	tv.tv_usec = (timeout % 1000) * 1000;
	tp = &tv;
    }
    n = select (fd + 1, 0, &set, 0, tp);
    if (n > 0)
	return TRUE;
    return FALSE;
}

static Bool
MouseWriteByte (int fd, unsigned char c, int timeout)
{
    int	ret;
    
#ifdef DEBUG_BYTES
    ErrorF ("\tput %02x\n", c);
#endif
    for (;;)
    {
	ret = write (fd, &c, 1);
	if (ret == 1)
	    return TRUE;
	if (ret == 0)
	    return FALSE;
	if (errno != EWOULDBLOCK)
	    return FALSE;
	if (!MouseWaitForWritable (fd, timeout))
	    return FALSE;
    }
}

static Bool
MouseWriteBytes (int fd, unsigned char *c, int n, int timeout)
{
    while (n--)
	if (!MouseWriteByte (fd, *c++, timeout))
	    return FALSE;
    return TRUE;
}

#define MAX_MOUSE   10	    /* maximum length of mouse protocol */
#define MAX_SKIP    16	    /* number of error bytes before switching */
#define MAX_VALID   4	    /* number of valid packets before accepting */

typedef struct _kmouseProt {
    char	    *name;
    Bool	    (*Complete) (KdPointerInfo *pi, unsigned char *ev, int ne);
    int		    (*Valid) (KdPointerInfo *pi, unsigned char *ev, int ne);
    Bool	    (*Parse) (KdPointerInfo *pi, unsigned char *ev, int ne);
    Bool	    (*Init) (KdPointerInfo *pi);
    unsigned char   headerMask, headerValid;
    unsigned char   dataMask, dataValid;
    Bool	    tty;
    unsigned int    c_iflag;
    unsigned int    c_oflag;
    unsigned int    c_lflag;
    unsigned int    c_cflag;
    unsigned int    speed;
    unsigned char   *init;
    unsigned long   state;
} KmouseProt;

typedef enum _kmouseStage {
    MouseBroken, MouseTesting, MouseWorking
} KmouseStage;

typedef struct _kmouse {
    Kbufio		iob;
    const KmouseProt	*prot;
    int			i_prot;
    KmouseStage		stage;	/* protocol verification stage */
    Bool		tty;	/* mouse device is a tty */
    int			valid;	/* sequential valid events */
    int			tested;	/* bytes scanned during Testing phase */
    int			invalid;/* total invalid bytes for this protocol */
    unsigned long	state;	/* private per protocol, init to prot->state */
} Kmouse;
    
static int mouseValid (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse		*km = pi->driverPrivate;
    const KmouseProt	*prot = km->prot;
    int	    i;

    for (i = 0; i < ne; i++)
	if ((ev[i] & prot->headerMask) == prot->headerValid)
	    break;
    if (i != 0)
	return i;
    for (i = 1; i < ne; i++)
	if ((ev[i] & prot->dataMask) != prot->dataValid)
	    return -1;
    return 0;
}

static Bool threeComplete (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    return ne == 3;
}

static Bool fourComplete (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    return ne == 4;
}

static Bool fiveComplete (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    return ne == 5;
}

static Bool MouseReasonable (KdPointerInfo *pi, unsigned long flags, int dx, int dy)
{
    Kmouse		*km = pi->driverPrivate;

    if (km->stage == MouseWorking) 
	return TRUE;
    if (dx < -50 || dx > 50) 
    {
#ifdef DEBUG
	ErrorF ("Large X %d\n", dx);
#endif
	return FALSE;
    }
    if (dy < -50 || dy > 50) 
    {
#ifdef DEBUG
	ErrorF ("Large Y %d\n", dy);
#endif
	return FALSE;
    }
    return TRUE;
}

/*
 * Standard PS/2 mouse protocol
 */
static Bool ps2Parse (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse	    *km = pi->driverPrivate;
    int		    dx, dy, dz;
    unsigned long   flags;
    unsigned long   flagsrelease = 0;
    
    flags = KD_MOUSE_DELTA;
    if (ev[0] & 4)
	flags |= KD_BUTTON_2;
    if (ev[0] & 2)
	flags |= KD_BUTTON_3;
    if (ev[0] & 1)
	flags |= KD_BUTTON_1;
    
    if (ne > 3)
    {
	dz = (int) (signed char) ev[3];
	if (dz < 0)
	{
	    flags |= KD_BUTTON_4;
	    flagsrelease = KD_BUTTON_4;
	}
	else if (dz > 0)
	{
	    flags |= KD_BUTTON_5;
	    flagsrelease = KD_BUTTON_5;
	}
    }
	
    dx = ev[1];
    if (ev[0] & 0x10)
	dx -= 256;
    dy = ev[2];
    if (ev[0] & 0x20)
	dy -= 256;
    dy = -dy;
    if (!MouseReasonable (pi, flags, dx, dy))
	return FALSE;
    if (km->stage == MouseWorking)
    {
	KdEnqueuePointerEvent (pi, flags, dx, dy, 0);
	if (flagsrelease)
	{
	    flags &= ~flagsrelease;
	    KdEnqueuePointerEvent (pi, flags, dx, dy, 0);
	}
    }
    return TRUE;
}

static Bool ps2Init (KdPointerInfo *pi);

static const KmouseProt ps2Prot = {
    "ps/2",
    threeComplete, mouseValid, ps2Parse, ps2Init,
    0x08, 0x08, 0x00, 0x00,
    FALSE
};

static const KmouseProt imps2Prot = {
    "imps/2",
    fourComplete, mouseValid, ps2Parse, ps2Init,
    0x08, 0x08, 0x00, 0x00,
    FALSE
};

static const KmouseProt exps2Prot = {
    "exps/2",
    fourComplete, mouseValid, ps2Parse, ps2Init,
    0x08, 0x08, 0x00, 0x00,
    FALSE
};

/*
 * Once the mouse is known to speak ps/2 protocol, go and find out
 * what advanced capabilities it has and turn them on
 */

/* these extracted from FreeBSD 4.3 sys/dev/kbd/atkbdcreg.h */

/* aux device commands (sent to KBD_DATA_PORT) */
#define PSMC_SET_SCALING11      0x00e6
#define PSMC_SET_SCALING21      0x00e7
#define PSMC_SET_RESOLUTION     0x00e8
#define PSMC_SEND_DEV_STATUS    0x00e9
#define PSMC_SET_STREAM_MODE    0x00ea
#define PSMC_SEND_DEV_DATA      0x00eb
#define PSMC_SET_REMOTE_MODE    0x00f0
#define PSMC_SEND_DEV_ID        0x00f2
#define PSMC_SET_SAMPLING_RATE  0x00f3
#define PSMC_ENABLE_DEV         0x00f4
#define PSMC_DISABLE_DEV        0x00f5
#define PSMC_SET_DEFAULTS       0x00f6
#define PSMC_RESET_DEV          0x00ff

/* PSMC_SET_RESOLUTION argument */
#define PSMD_RES_LOW            0       /* typically 25ppi */
#define PSMD_RES_MEDIUM_LOW     1       /* typically 50ppi */
#define PSMD_RES_MEDIUM_HIGH    2       /* typically 100ppi (default) */
#define PSMD_RES_HIGH           3       /* typically 200ppi */
#define PSMD_MAX_RESOLUTION     PSMD_RES_HIGH

/* PSMC_SET_SAMPLING_RATE */
#define PSMD_MAX_RATE           255     /* FIXME: not sure if it's possible */

/* aux device ID */
#define PSM_MOUSE_ID            0
#define PSM_BALLPOINT_ID        2
#define PSM_INTELLI_ID          3
#define PSM_EXPLORER_ID         4
#define PSM_4DMOUSE_ID          6
#define PSM_4DPLUS_ID           8

static unsigned char	ps2_init[] = { 
    PSMC_ENABLE_DEV,
    0,
};

#define NINIT_PS2   1

static unsigned char    wheel_3button_init[] = { 
    PSMC_SET_SAMPLING_RATE, 200,
    PSMC_SET_SAMPLING_RATE, 100,
    PSMC_SET_SAMPLING_RATE,  80,
    PSMC_SEND_DEV_ID,
    0,
};

#define NINIT_IMPS2 4

static unsigned char    wheel_5button_init[] = {
    PSMC_SET_SAMPLING_RATE, 200,
    PSMC_SET_SAMPLING_RATE, 100,
    PSMC_SET_SAMPLING_RATE,  80,
    PSMC_SET_SAMPLING_RATE, 200,
    PSMC_SET_SAMPLING_RATE, 200,
    PSMC_SET_SAMPLING_RATE,  80,
    PSMC_SEND_DEV_ID, 
    0
};

#define NINIT_EXPS2 7

static unsigned char	intelli_init[] = {
    PSMC_SET_SAMPLING_RATE, 200,
    PSMC_SET_SAMPLING_RATE, 100, 
    PSMC_SET_SAMPLING_RATE,  80, 
    0
};

#define NINIT_INTELLI	3

static int
ps2SkipInit (KdPointerInfo *pi, int ninit, Bool ret_next)
{
    Kmouse  *km = pi->driverPrivate;
    int	    c = -1;
    int	    skipping;
    Bool    waiting;
    
    skipping = 0;
    waiting = FALSE;
    while (ninit || ret_next)
    {
	c = MouseReadByte (&km->iob, MOUSE_TIMEOUT);
	if (c == -1)
	    break;
	/* look for ACK */
	if (c == 0xfa)
	{
	    ninit--;
	    if (ret_next)
		waiting = TRUE;
	}
	/* look for packet start -- not the response */
	else if ((c & 0x08) == 0x08)
	    waiting = FALSE;
	else if (waiting)
	    break;
    }
    return c;
}

static Bool
ps2Init (KdPointerInfo *pi)
{
    Kmouse	    *km = pi->driverPrivate;
    int		    skipping;
    Bool	    waiting;
    int		    id;
    unsigned char   *init;
    int		    ninit;
    
    /* Send Intellimouse initialization sequence */
    MouseWriteBytes (km->iob.fd, intelli_init, strlen ((char *) intelli_init), 100);
    /*
     * Send ID command
     */
    if (!MouseWriteByte (km->iob.fd, PSMC_SEND_DEV_ID, 100))
	return FALSE;
    skipping = 0;
    waiting = FALSE;
    id = ps2SkipInit (pi, 0, TRUE);
    switch (id) {
    case 3:
	init = wheel_3button_init;
	ninit = NINIT_IMPS2;
	km->prot = &imps2Prot;
	break;
    case 4:
	init = wheel_5button_init;
	ninit = NINIT_EXPS2;
	km->prot = &exps2Prot;
	break;
    default:
	init = ps2_init;
	ninit = NINIT_PS2;
	km->prot = &ps2Prot;
	break;
    }
    if (init)
	MouseWriteBytes (km->iob.fd, init, strlen ((char *) init), 100);
    /*
     * Flush out the available data to eliminate responses to the
     * initialization string.  Make sure any partial event is
     * skipped
     */
    (void) ps2SkipInit (pi, ninit, FALSE);
    return TRUE;
}

static Bool busParse (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse	    *km = pi->driverPrivate;
    int		    dx, dy;
    unsigned long   flags;
    
    flags = KD_MOUSE_DELTA;
    dx = (signed char) ev[1];
    dy = -(signed char) ev[2];
    if ((ev[0] & 4) == 0)
	flags |= KD_BUTTON_1;
    if ((ev[0] & 2) == 0)
	flags |= KD_BUTTON_2;
    if ((ev[0] & 1) == 0)
	flags |= KD_BUTTON_3;
    if (!MouseReasonable (pi, flags, dx, dy))
	return FALSE;
    if (km->stage == MouseWorking)
	KdEnqueuePointerEvent (pi, flags, dx, dy, 0);
    return TRUE;
}

static const KmouseProt busProt = {
    "bus",
    threeComplete, mouseValid, busParse, 0,
    0xf8, 0x00, 0x00, 0x00,
    FALSE
};

/*
 * Standard MS serial protocol, three bytes
 */

static Bool msParse (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse	    *km = pi->driverPrivate;
    int		    dx, dy;
    unsigned long   flags;

    flags = KD_MOUSE_DELTA;

    if (ev[0] & 0x20)
	flags |= KD_BUTTON_1;
    if (ev[0] & 0x10)
	flags |= KD_BUTTON_3;

    dx = (signed char)(((ev[0] & 0x03) << 6) | (ev[1] & 0x3F));
    dy = (signed char)(((ev[0] & 0x0C) << 4) | (ev[2] & 0x3F));
    if (!MouseReasonable (pi, flags, dx, dy))
	return FALSE;
    if (km->stage == MouseWorking)
	KdEnqueuePointerEvent (pi, flags, dx, dy, 0);
    return TRUE;
}

static const KmouseProt msProt = {
    "ms",
    threeComplete, mouseValid, msParse, 0,
    0xc0, 0x40, 0xc0, 0x00,
    TRUE,
    IGNPAR,
    0,
    0,
    CS7 | CSTOPB | CREAD | CLOCAL,
    B1200,
};

/*
 * Logitech mice send 3 or 4 bytes, the only way to tell is to look at the
 * first byte of a synchronized protocol stream and see if it's got
 * any bits turned on that can't occur in that fourth byte
 */
static Bool logiComplete (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse		*km = pi->driverPrivate;

    if ((ev[0] & 0x40) == 0x40)
	return ne == 3;
    if (km->stage != MouseBroken && (ev[0] & ~0x23) == 0)
	return ne == 1;
    return FALSE;
}

static int logiValid (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse		*km = pi->driverPrivate;
    const KmouseProt	*prot = km->prot;
    int	    i;

    for (i = 0; i < ne; i++)
    {
	if ((ev[i] & 0x40) == 0x40)
	    break;
	if (km->stage != MouseBroken && (ev[i] & ~0x23) == 0)
	    break;
    }
    if (i != 0)
	return i;
    for (i = 1; i < ne; i++)
	if ((ev[i] & prot->dataMask) != prot->dataValid)
	    return -1;
    return 0;
}

static Bool logiParse (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse	    *km = pi->driverPrivate;
    int		    dx, dy;
    unsigned long   flags;

    flags = KD_MOUSE_DELTA;
    
    if (ne == 3)
    {
	if (ev[0] & 0x20)
	    flags |= KD_BUTTON_1;
	if (ev[0] & 0x10)
	    flags |= KD_BUTTON_3;
    
	dx = (signed char)(((ev[0] & 0x03) << 6) | (ev[1] & 0x3F));
	dy = (signed char)(((ev[0] & 0x0C) << 4) | (ev[2] & 0x3F));
	flags |= km->state & KD_BUTTON_2;
    }
    else
    {
	if (ev[0] & 0x20)
	    flags |= KD_BUTTON_2;
	dx = 0;
	dy = 0;
	flags |= km->state & (KD_BUTTON_1|KD_BUTTON_3);
    }

    if (!MouseReasonable (pi, flags, dx, dy))
	return FALSE;
    if (km->stage == MouseWorking)
	KdEnqueuePointerEvent (pi, flags, dx, dy, 0);
    return TRUE;
}

static const KmouseProt logiProt = {
    "logitech",
    logiComplete, logiValid, logiParse, 0,
    0xc0, 0x40, 0xc0, 0x00,
    TRUE,
    IGNPAR,
    0,
    0,
    CS7 | CSTOPB | CREAD | CLOCAL,
    B1200,
};

/*
 * Mouse systems protocol, 5 bytes
 */
static Bool mscParse (KdPointerInfo *pi, unsigned char *ev, int ne)
{
    Kmouse	    *km = pi->driverPrivate;
    int		    dx, dy;
    unsigned long   flags;

    flags = KD_MOUSE_DELTA;
    
    if (!(ev[0] & 0x4))
	flags |= KD_BUTTON_1;
    if (!(ev[0] & 0x2))
	flags |= KD_BUTTON_2;
    if (!(ev[0] & 0x1))
	flags |= KD_BUTTON_3;
    dx =    (signed char)(ev[1]) + (signed char)(ev[3]);
    dy = - ((signed char)(ev[2]) + (signed char)(ev[4]));

    if (!MouseReasonable (pi, flags, dx, dy))
	return FALSE;
    if (km->stage == MouseWorking)
	KdEnqueuePointerEvent (pi, flags, dx, dy, 0);
    return TRUE;
}

static const KmouseProt mscProt = {
    "msc",
    fiveComplete, mouseValid, mscParse, 0,
    0xf8, 0x80, 0x00, 0x00,
    TRUE,
    IGNPAR,
    0,
    0,
    CS8 | CSTOPB | CREAD | CLOCAL,
    B1200,
};

/*
 * Use logitech before ms -- they're the same except that
 * logitech sometimes has a fourth byte
 */
static const KmouseProt *kmouseProts[] = {
    &ps2Prot, &imps2Prot, &exps2Prot, &busProt, &logiProt, &msProt, &mscProt,
};

#define NUM_PROT    (sizeof (kmouseProts) / sizeof (kmouseProts[0]))

static void
MouseInitProtocol (Kmouse *km)
{
    int		    ret;
    struct termios  t;

    if (km->prot->tty)
    {
	ret = tcgetattr (km->iob.fd, &t);

	if (ret >= 0)
	{
	    t.c_iflag = km->prot->c_iflag;
	    t.c_oflag = km->prot->c_oflag;
	    t.c_lflag = km->prot->c_lflag;
	    t.c_cflag = km->prot->c_cflag;
	    cfsetispeed (&t, km->prot->speed);
	    cfsetospeed (&t, km->prot->speed);
	    ret = tcsetattr (km->iob.fd, TCSANOW, &t);
	}
    }
    km->stage = MouseBroken;
    km->valid = 0;
    km->tested = 0;
    km->invalid = 0;
    km->state = km->prot->state;
}

static void
MouseFirstProtocol (Kmouse *km, char *prot)
{
    if (prot)
    {
	for (km->i_prot = 0; km->i_prot < NUM_PROT; km->i_prot++)
	    if (!strcmp (prot, kmouseProts[km->i_prot]->name))
		break;
	if (km->i_prot == NUM_PROT)
	{
	    int	i;
	    ErrorF ("Unknown mouse protocol \"%s\". Pick one of:", prot);
	    for (i = 0; i < NUM_PROT; i++)
		ErrorF (" %s", kmouseProts[i]->name);
	    ErrorF ("\n");
	}
	else
	{
	    km->prot = kmouseProts[km->i_prot];
	    if (km->tty && !km->prot->tty)
		ErrorF ("Mouse device is serial port, protocol %s is not serial protocol\n",
			prot);
	    else if (!km->tty && km->prot->tty)
		ErrorF ("Mouse device is not serial port, protocol %s is serial protocol\n",
			prot);
	}
    }
    if (!km->prot)
    {
	for (km->i_prot = 0; kmouseProts[km->i_prot]->tty != km->tty; km->i_prot++)
	    ;
	km->prot = kmouseProts[km->i_prot];
    }
    MouseInitProtocol (km);
}

static void
MouseNextProtocol (Kmouse *km)
{
    do
    {
	if (!km->prot)
	    km->i_prot = 0;
	else
	    if (++km->i_prot == NUM_PROT) km->i_prot = 0;
	km->prot = kmouseProts[km->i_prot];
    } while (km->prot->tty != km->tty);
    MouseInitProtocol (km);
    ErrorF ("Switching to mouse protocol \"%s\"\n", km->prot->name);
}

static void
MouseRead (int mousePort, void *closure)
{
    KdPointerInfo   *pi = closure;
    Kmouse	    *km = pi->driverPrivate;
    unsigned char   event[MAX_MOUSE];
    int		    ne;
    int		    c;
    int		    i;
    int		    timeout;

    timeout = 0;
    ne = 0;
    for(;;)
    {
	c = MouseReadByte (&km->iob, timeout);
	if (c == -1)
	{
	    if (ne)
	    {
		km->invalid += ne + km->tested;
		km->valid = 0;
		km->tested = 0;
		km->stage = MouseBroken;
	    }
	    break;
	}
	event[ne++] = c;
	i = (*km->prot->Valid) (pi, event, ne);
	if (i != 0)
	{
#ifdef DEBUG
	    ErrorF ("Mouse protocol %s broken %d of %d bytes bad\n",
		    km->prot->name, i > 0 ? i : ne, ne);
#endif
	    if (i > 0 && i < ne)
	    {
		ne -= i;
		memmove (event, event + i, ne);
	    }
	    else
	    {
		i = ne;
		ne = 0;
	    }
	    km->invalid += i + km->tested;
	    km->valid = 0;
	    km->tested = 0;
	    if (km->stage == MouseWorking)
		km->i_prot--;
	    km->stage = MouseBroken;
	    if (km->invalid > MAX_SKIP)
	    {
		MouseNextProtocol (km);
		ne = 0;
	    }
	    timeout = 0;
	}
	else
	{
	    if ((*km->prot->Complete) (pi, event, ne))
	    {
		if ((*km->prot->Parse) (pi, event, ne))
		{
		    switch (km->stage)
		    {
		    case MouseBroken:
#ifdef DEBUG			
			ErrorF ("Mouse protocol %s seems OK\n",
				km->prot->name);
#endif
			/* do not zero invalid to accumulate invalid bytes */
			km->valid = 0;
			km->tested = 0;
			km->stage = MouseTesting;
			/* fall through ... */
		    case MouseTesting:
			km->valid++;
			km->tested += ne;
			if (km->valid > MAX_VALID)
			{
#ifdef DEBUG
			    ErrorF ("Mouse protocol %s working\n",
				    km->prot->name);
#endif
			    km->stage = MouseWorking;
			    km->invalid = 0;
			    km->tested = 0;
			    km->valid = 0;
			    if (km->prot->Init && !(*km->prot->Init) (pi))
				km->stage = MouseBroken;
			}
			break;
		    case MouseWorking:
			break;
		    }
		}
		else
		{
		    km->invalid += ne + km->tested;
		    km->valid = 0;
		    km->tested = 0;
		    km->stage = MouseBroken;
		}
		ne = 0;
		timeout = 0;
	    }
	    else
		timeout = MOUSE_TIMEOUT;
	}
    }
}

int MouseInputType;

char *kdefaultMouse[] =  {
    "/dev/input/mice",
    "/dev/mouse",
    "/dev/psaux",
    "/dev/adbmouse",
    "/dev/ttyS0",
    "/dev/ttyS1",
};

#define NUM_DEFAULT_MOUSE    (sizeof (kdefaultMouse) / sizeof (kdefaultMouse[0]))

static Status
MouseInit (KdPointerInfo *pi)
{
    int		i;
    int		fd;
    Kmouse	*km;

    if (!pi)
        return BadImplementation;
    
    if (!pi->path || strcmp(pi->path, "auto") == 0) {
        for (i = 0; i < NUM_DEFAULT_MOUSE; i++) {
            fd = open (kdefaultMouse[i], 2);
            if (fd >= 0) {
                pi->path = KdSaveString (kdefaultMouse[i]);
                break;
            }
        }
    }
    else {
        fd = open (pi->path, 2);
    }
	    
    if (fd < 0)
        return BadMatch;

    close(fd);

    km = (Kmouse *) xalloc (sizeof (Kmouse));
    if (km) {
        km->iob.avail = km->iob.used = 0;
        MouseFirstProtocol(km, "exps/2");
        km->i_prot = 0;
        km->tty = isatty (fd);
        km->iob.fd = -1;
        pi->driverPrivate = km;
    }
    else {
        close (fd);
        return BadAlloc;
    }

    return Success;
}

static Status
MouseEnable (KdPointerInfo *pi)
{
    Kmouse *km;

    if (!pi || !pi->driverPrivate || !pi->path)
        return BadImplementation;

    km = pi->driverPrivate;

    km->iob.fd = open(pi->path, 2);
    if (km->iob.fd < 0)
        return BadMatch;

    if (!KdRegisterFd (km->iob.fd, MouseRead, pi))
    {
        close(km->iob.fd);
        return BadAlloc;
    }

    return Success;
}

static void
MouseDisable (KdPointerInfo *pi)
{
    Kmouse *km;
    if (!pi || !pi->driverPrivate)
        return;
    
    km = pi->driverPrivate;
    KdUnregisterFd (pi, km->iob.fd, TRUE);
}

static void
MouseFini (KdPointerInfo *pi)
{
    xfree (pi->driverPrivate);
    pi->driverPrivate = NULL;
}

KdPointerDriver LinuxMouseDriver = {
    "mouse",
    MouseInit,
    MouseEnable,
    MouseDisable,
    MouseFini,
    NULL,
};