/*
 * mice.c - mouse definitions for gpm-Linux
 *
 * Copyright (C) 1993        Andrew Haylett <ajh@gec-mrc.co.uk>
 * Copyright (C) 1994-2000   Alessandro Rubini <rubini@linux.it>
 * Copyright (C) 1998,1999   Ian Zimmerman <itz@rahul.net>
 * Copyright (C) 2001-2008   Nico Schottelius <nico-gpm2008 at schottelius.org>
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 ********/

/*
 * This file is part of the mouse server. The information herein
 * is kept aside from the rest of the server to ease fixing mouse-type
 * issues. Each mouse type is expected to fill the `buttons', `dx' and `dy'
 * fields of the Gpm_Event structure and nothing more.
 *
 * Absolute-pointing devices (support by Marc Meis), are expecting to
 * fit `x' and `y' as well. Unfortunately, to do it the window size must
 * be accessed. The global variable "win" is available for that use.
 *
 * The `data' parameter points to a byte-array with event data, as read
 * by the mouse device. The mouse device should return a fixed number of
 * bytes to signal an event, and that exact number is read by the server
 * before calling one of these functions.
 *
 * The conversion function defined here should return 0 on success and -1
 * on failure.
 *
 * Refer to the definition of Gpm_Type to probe further.
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>

#include <sys/types.h>
#include <sys/stat.h> /* stat() */
#include <sys/time.h> /* select() */

#include <linux/kdev_t.h> /* MAJOR */
#include <linux/keyboard.h>

/* JOYSTICK */
#ifdef HAVE_LINUX_JOYSTICK_H
#include <linux/joystick.h>
#endif

/* EV DEVICE */
#ifdef HAVE_LINUX_INPUT_H
#include <linux/input.h>
#endif /* HAVE_LINUX_INPUT_H */
 


#include "headers/gpmInt.h"
#include "headers/daemon.h"
#include "headers/twiddler.h"
#include "headers/synaptics.h"
#include "headers/message.h"


/*========================================================================*/
/* Parsing argv: helper dats struct function (should they get elsewhere?) */
/*========================================================================*/

enum argv_type {
   ARGV_BOOL = 1,
   ARGV_INT, /* "%i" */
   ARGV_DEC, /* "%d" */
   ARGV_STRING,
   /* other types must be added */
   ARGV_END = 0
};

typedef struct argv_helper {
   char *name;
   enum argv_type type;
   union u {
      int *iptr;   /* used for int and bool arguments */
      char **sptr; /* used for string arguments, by strdup()ing the value */
   } u;
   int value; /* used for boolean arguments */
} argv_helper;

static int parse_argv(argv_helper *info, int argc, char **argv)
{
   int i, j = 0, errors = 0;
   long l;
   argv_helper *p;
   char *s, *t;
   int base = 0; /* for strtol */


   for (i=1; i<argc; i++) {
      for (p = info; p->type != ARGV_END; p++) {
         j = strlen(p->name);
         if (strncmp(p->name, argv[i], j))
            continue;
         if (isalnum(argv[i][j]))
            continue;
         break;
      }
      if (p->type == ARGV_END) { /* not found */
         fprintf(stderr, "%s: Uknown option \"%s\" for pointer \"%s\"\n",
                  option.progname, argv[i], argv[0]);
         errors++;
         continue;
      }
      /* Found. Look for trailing stuff, if any */
      s = argv[i]+j;
      while (*s && isspace(*s)) s++; /* skip spaces */
      if (*s == '=') s++; /* skip equal */
      while (*s && isspace(*s)) s++; /* skip other spaces */

      /* Now parse what s is */
      switch(p->type) {
         case ARGV_BOOL:
            if (*s) {
               gpm_report(GPM_PR_ERR,GPM_MESS_OPTION_NO_ARG, option.progname,p->name,s);
               errors++;
            }
            *(p->u.iptr) = p->value;
            break;

         case ARGV_DEC:
            base = 10; /* and fall through */
         case ARGV_INT:
            l = strtol(s, &t, base);
            if (*t) {
               gpm_report(GPM_PR_ERR,GPM_MESS_INVALID_ARG, option.progname, s, p->name);
               errors++;
               break;
            }
            *(p->u.iptr) = (int)l;
            break;

         case ARGV_STRING:
            *(p->u.sptr) = strdup(s);
            break;

         case ARGV_END: /* let's please "-Wall" */
            break;
      }
   } /* for i in argc */
   if (errors) gpm_report(GPM_PR_ERR,GPM_MESS_CONT_WITH_ERR, option.progname);
   return errors;
}

/*========================================================================*/
/* Provide a common error engine by parsing with an empty option-set */
/*========================================================================*/
static volatile int check_no_argv(int argc, char **argv)
{
   static argv_helper optioninfo[] = {
      {"",       ARGV_END}
      };
   return parse_argv(optioninfo, argc, argv);
}

/*========================================================================*/
/* Parse the "old" -o options */
/*========================================================================*/
static int option_modem_lines(int fd, int argc, char **argv)
{
   static unsigned int err, lines, reallines;

   static argv_helper optioninfo[] = {
      {"dtr",  ARGV_BOOL, u: {iptr: &lines}, value: TIOCM_DTR},
      {"rts",  ARGV_BOOL, u: {iptr: &lines}, value: TIOCM_RTS},
      {"both", ARGV_BOOL, u: {iptr: &lines}, value: TIOCM_DTR | TIOCM_RTS},
      {"",       ARGV_END}
      };

   if (argc<2) return 0;
   if (argc > 2) {
      gpm_report(GPM_PR_ERR,GPM_MESS_TOO_MANY_OPTS,option.progname, argv[0]);
      errno = EINVAL; /* used by gpm_oops(), if the caller reports failure */
      return -1;
   }
   err = parse_argv(optioninfo, argc, argv);
   if(err) return 0; /* a message has been printed, but go on as good */

   /* ok, move the lines */
   ioctl(fd, TIOCMGET, &reallines);
   reallines &= ~lines;
   ioctl(fd, TIOCMSET, &reallines);
   return 0;
}

/*========================================================================*/
/*  real absolute coordinates for absolute devices,  not very clean */
/*========================================================================*/

#define REALPOS_MAX 16383 /*  min 0 max=16383, but due to change.  */
int realposx=-1, realposy=-1;

/*========================================================================*/
/* 
 * When repeating, it is important not to try to repeat more bits of dx and
 * dy than the protocol can handle.  Otherwise, you may end up repeating the
 * low bits of a large value, which causes erratic motion.
 */
/*========================================================================*/

static int limit_delta(int delta, int min, int max)
{
   return delta > max ? max :
          delta < min ? min : delta;
}

/*========================================================================*/
/*
 * Ok, here we are: first, provide the functions; initialization is later.
 * The return value is the number of unprocessed bytes
 */
/*========================================================================*/

#ifdef HAVE_LINUX_INPUT_H
static int M_evdev (Gpm_Event * state, unsigned char *data)
{
   struct input_event thisevent;
   (void) memcpy (&thisevent, data, sizeof (struct input_event));
   if (thisevent.type == EV_REL) {
      if (thisevent.code == REL_X)
         state->dx = (signed char) thisevent.value;
      else if (thisevent.code == REL_Y)
         state->dy = (signed char) thisevent.value;
   } else if (thisevent.type == EV_KEY) {
      switch(thisevent.code) {
         case BTN_LEFT:    state->buttons ^= GPM_B_LEFT;    break;
         case BTN_MIDDLE:  state->buttons ^= GPM_B_MIDDLE;  break;
         case BTN_RIGHT:   state->buttons ^= GPM_B_RIGHT;   break;
         case BTN_SIDE:    state->buttons ^= GPM_B_MIDDLE;  break;
      }   
   }
   return 0;
}
#endif /* HAVE_LINUX_INPUT_H */

static int M_ms(Gpm_Event *state,  unsigned char *data)
{
   /*
    * some devices report a change of middle-button state by
    * repeating the current button state  (patch by Mark Lord)
    */
   static unsigned char prev=0;

   if (data[0] == 0x40 && !(prev|data[1]|data[2]))
      state->buttons = GPM_B_MIDDLE; /* third button on MS compatible mouse */
   else
      state->buttons= ((data[0] & 0x20) >> 3) | ((data[0] & 0x10) >> 4);
   prev = state->buttons;
   state->dx=      (signed char)(((data[0] & 0x03) << 6) | (data[1] & 0x3F));
   state->dy=      (signed char)(((data[0] & 0x0C) << 4) | (data[2] & 0x3F));

   return 0;
}

static int M_ms_plus(Gpm_Event *state, unsigned char *data)
{
   static unsigned char prev=0;

   state->buttons= ((data[0] & 0x20) >> 3) | ((data[0] & 0x10) >> 4);
   state->dx=      (signed char)(((data[0] & 0x03) << 6) | (data[1] & 0x3F));
   state->dy=      (signed char)(((data[0] & 0x0C) << 4) | (data[2] & 0x3F));

   /* Allow motion *and* button change (Michael Plass) */

   if((state->dx==0) &&(state->dy==0) && (state->buttons==(prev&~GPM_B_MIDDLE)))
      state->buttons = prev^GPM_B_MIDDLE;  /* no move or change: toggle middle */
   else
      state->buttons |= prev&GPM_B_MIDDLE;    /* change: preserve middle */

   prev=state->buttons;

   return 0;
}

static int M_ms_plus_lr(Gpm_Event *state,  unsigned char *data)
{
   /*
    * Same as M_ms_plus but with an addition by Edmund GRIMLEY EVANS
    */
   static unsigned char prev=0;

   state->buttons= ((data[0] & 0x20) >> 3) | ((data[0] & 0x10) >> 4);
   state->dx=      (signed char)(((data[0] & 0x03) << 6) | (data[1] & 0x3F));
   state->dy=      (signed char)(((data[0] & 0x0C) << 4) | (data[2] & 0x3F));

   /* Allow motion *and* button change (Michael Plass) */

   if((state->dx==0)&& (state->dy==0) && (state->buttons==(prev&~GPM_B_MIDDLE)))
      state->buttons = prev^GPM_B_MIDDLE; /* no move or change: toggle middle */
   else
      state->buttons |= prev&GPM_B_MIDDLE;/* change: preserve middle */

   /* Allow the user to reset state of middle button by pressing
      the other two buttons at once (Edmund GRIMLEY EVANS) */

   if (!((~state->buttons)&(GPM_B_LEFT|GPM_B_RIGHT)) &&
      ((~prev)&(GPM_B_LEFT|GPM_B_RIGHT)))
      state->buttons &= ~GPM_B_MIDDLE;

   prev=state->buttons;

   return 0;
}

/* Summagraphics/Genius/Acecad tablet support absolute mode*/
/* Summagraphics MM-Series format*/
/* (Frank Holtz) hof@bigfoot.de Tue Feb 23 21:04:09 MET 1999 */
int SUMMA_BORDER=100;
int summamaxx,summamaxy;
char summaid=-1;
static int M_summa(Gpm_Event *state, unsigned char *data)
{
   int x, y;

   x = ((data[2]<<7) | data[1])-SUMMA_BORDER;
   if (x<0) x=0;
   if (x>summamaxx) x=summamaxx;
   state->x = (x * win.ws_col / summamaxx);
   realposx = (x * 16383 / summamaxx);

   y = ((data[4]<<7) | data[3])-SUMMA_BORDER;
   if (y<0) y=0; if (y>summamaxy) y=summamaxy;
   state->y = 1 + y * (win.ws_row-1)/summamaxy;
   realposy = y * 16383 / summamaxy;  

   state->buttons=
    !!(data[0]&1) * GPM_B_LEFT +
    !!(data[0]&2) * GPM_B_RIGHT +
    !!(data[0]&4) * GPM_B_MIDDLE;

   return 0;
}

/* ps2 */
static int R_ps2(Gpm_Event *state, int fd)
{
   signed char buffer[3];

   buffer[0]=((state->buttons & GPM_B_LEFT  ) > 0)*1 +
             ((state->buttons & GPM_B_RIGHT ) > 0)*2 +
             ((state->buttons & GPM_B_MIDDLE) > 0)*4;
   buffer[0] |= 8 +
             ((state->dx < 0) ? 0x10 : 0) +
             ((state->dy > 0) ? 0x20 : 0);
   buffer[1] = limit_delta(state->dx, -128, 127);
   buffer[2] = limit_delta(-state->dy, -128, 127);
   return write(fd,buffer,3);
}


/* Thu Jan 28 20:54:47 MET 1999 hof@hof-berlin.de SummaSketch reportformat */
static int R_summa(Gpm_Event *state, int fd)
{
   signed char buffer[5];
   static int x,y;
  
   if (realposx==-1) { /* real absolute device? */
      x=x+(state->dx*25); if (x<0) x=0; if (x>16383) x=16383;
      y=y+(state->dy*25); if (y<0) y=0; if (y>16383) y=16383;
   } else { /* yes */
      x=realposx; y=realposy;
   }

   buffer[0]=0x98+((state->buttons&GPM_B_LEFT)>0?1:0)+
                  ((state->buttons&GPM_B_MIDDLE)>0?2:0)+
                  ((state->buttons&GPM_B_RIGHT)>0?3:0);
   buffer[1]=x & 0x7f;  /* X0-6*/
   buffer[2]=(x >> 7) & 0x7f;  /* X7-12*/
   buffer[3]=y & 0x7f;  /* Y0-6*/
   buffer[4]=(y >> 7) & 0x7f;  /* Y7-12*/

   return write(fd,buffer,5);
}



/* 'Genitizer' (kw@dtek.chalmers.se 11/12/97) */
static int M_geni(Gpm_Event *state,  unsigned char *data)
{
   /* this is a little confusing. If we use the stylus, we
    * have three buttons (tip, lower, upper), and if
    * we use the puck we have four buttons. (And the
    * protocol is a little mangled if several of the buttons
    * on the puck are pressed simultaneously. 
    * I don't use the puck, hence I try to decode three buttons
    * only. tip = left, lower = middle, upper = right
    */
   state->buttons = 
      (data[0] & 0x01)<<2 | 
      (data[0] & 0x02) |
      (data[0] & 0x04)>>2;

   state->dx = ((data[1] & 0x3f) ) * ((data[0] & 0x10)?1:-1);   
   state->dy = ((data[2] & 0x3f) ) * ((data[0] & 0x8)?-1:1);

   return 0;
}


/* m$ 'Intellimouse' (steveb 20/7/97) */
static int M_ms3(Gpm_Event *state,  unsigned char *data)
{
   state->wdx = state->wdy = 0;
   state->buttons= ((data[0] & 0x20) >> 3)   /* left */
      | ((data[3] & 0x10) >> 3)   /* middle */
      | ((data[0] & 0x10) >> 4)   /* right */
      | (((data[3] & 0x0f) == 0x0f) * GPM_B_UP)     /* wheel up */
      | (((data[3] & 0x0f) == 0x01) * GPM_B_DOWN);  /* wheel down */
   state->dx = (signed char) (((data[0] & 0x03) << 6) | (data[1] & 0x3F));
   state->dy = (signed char) (((data[0] & 0x0C) << 4) | (data[2] & 0x3F));

   switch (data[3] & 0x0f) {
      case 0x0e: state->wdx = +1; break;
      case 0x02: state->wdx = -1; break;
      case 0x0f: state->wdy = +1; break;
      case 0x01: state->wdy = -1; break;
   }

   return 0;
}

static int R_ms3(Gpm_Event *state, int fd)
{

   int dx, dy;
   char buf[4] = {0, 0, 0, 0};
   
   buf[0] |= 0x40;
   
   if (state->buttons & GPM_B_LEFT)     buf[0] |= 0x20;
   if (state->buttons & GPM_B_MIDDLE)   buf[3] |= 0x10;
   if (state->buttons & GPM_B_RIGHT)    buf[0] |= 0x10;
   if (state->buttons & GPM_B_UP)       buf[3] |= 0x0f;
   if (state->buttons & GPM_B_DOWN)     buf[3] |= 0x01;

   dx = limit_delta(state->dx, -128, 127);
   buf[1]   = dx & ~0xC0;
   buf[0]  |= (dx & 0xC0) >> 6;

   dy = limit_delta(state->dy, -128, 127);
   buf[2] = dy & ~0xC0;
   buf[0] |= (dy & 0xC0) >> 4;

   /* wheel */
   if       (state->wdy > 0)   buf[3] |= 0x0f;
   else if  (state->wdy < 0)   buf[3] |= 0x01;
   
   return write(fd,buf,4);
}

/* M_brw is a variant of m$ 'Intellimouse' the middle button is different */
static int M_brw(Gpm_Event *state,  unsigned char *data)
{
   state->buttons= ((data[0] & 0x20) >> 3)   /* left */
      | ((data[3] & 0x20) >> 4)   /* middle */
      | ((data[0] & 0x10) >> 4);   /* right */
   state->dx=      (signed char)(((data[0] & 0x03) << 6) | (data[1] & 0x3F));
   state->dy=      (signed char)(((data[0] & 0x0C) << 4) | (data[2] & 0x3F));
   if (((data[0]&0xC0) != 0x40)||
      ((data[1]&0xC0) != 0x00)||
      ((data[2]&0xC0) != 0x00)||
      ((data[3]&0xC0) != 0x00)) {
      gpm_report(GPM_PR_DEBUG,GPM_MESS_SKIP_DATAP,data[0],data[1],data[2],data[3]);
      return -1;
   }
   /* wheel (dz) is (data[3] & 0x0f) */
   /* where is the side button? I can sort of detect it at 9600 baud */
   /* Note this mouse is very noisy */

   return 0;
}

static int M_bare(Gpm_Event *state,  unsigned char *data)
{
   /* a bare ms protocol */
   state->buttons= ((data[0] & 0x20) >> 3) | ((data[0] & 0x10) >> 4);
   state->dx=      (signed char)(((data[0] & 0x03) << 6) | (data[1] & 0x3F));
   state->dy=      (signed char)(((data[0] & 0x0C) << 4) | (data[2] & 0x3F));
   return 0;
}

static int M_sun(Gpm_Event *state,  unsigned char *data)
{
   state->buttons= (~data[0]) & 0x07;
   state->dx=      (signed char)(data[1]);
   state->dy=     -(signed char)(data[2]);
   return 0;
}

static int R_sun(Gpm_Event *state, int fd)
{
  signed char buffer[3];

  buffer[0]= (state->buttons ^ 0x07) | 0x80;
  buffer[1]= state->dx;
  buffer[2]= -(state->dy);
  return write(fd,buffer,3);
}

static int M_msc(Gpm_Event *state,  unsigned char *data)
{
   state->buttons= (~data[0]) & 0x07;
   state->dx=      (signed char)(data[1]) + (signed char)(data[3]);
   state->dy=     -((signed char)(data[2]) + (signed char)(data[4]));
   return 0;
}

/* itz Mon Jan 11 23:51:38 PST 1999 this code moved here from gpm.c */
/* (processMouse) */

static int R_msc(Gpm_Event *state, int fd)
{
   signed char buffer[5];
   int dx, dy;

   /* sluggish... */
   buffer[0]=(state->buttons ^ 0x07) | 0x80;
   dx = limit_delta(state->dx, -256, 254);
   buffer[3] =  state->dx - (buffer[1] = state->dx/2); /* Markus */
   dy = limit_delta(state->dy, -256, 254);
   buffer[4] = -state->dy - (buffer[2] = -state->dy/2);
   return write(fd,buffer,5);

}
                       
static int R_imps2(Gpm_Event *state, int fd)
{
   signed char buffer[4];
   int dx, dy;

   dx = limit_delta(state->dx, -256, 255);
   dy = limit_delta(state->dy, -256, 255);

   buffer[0] = 8 |
      (state->buttons & GPM_B_LEFT ? 1 : 0) |
      (state->buttons & GPM_B_MIDDLE ? 4 : 0) |
      (state->buttons & GPM_B_RIGHT ? 2 : 0) | 
      (dx < 0 ? 0x10 : 0) |
      (dy > 0 ? 0x20 : 0);
   buffer[1] = dx & 0xFF;
   buffer[2] = (-dy) & 0xFF;
   if (state->wdy > 0) buffer[3] = 0xff;
   if (state->wdy < 0) buffer[3] = 0x01;
   if (state->wdx > 0) buffer[3] = 0xfe;
   if (state->wdx < 0) buffer[3] = 0x02;
  
   return write(fd,buffer,4);

}

static int M_logimsc(Gpm_Event *state,  unsigned char *data) /* same as msc */
{
   state->buttons= (~data[0]) & 0x07;
   state->dx=      (signed char)(data[1]) + (signed char)(data[3]);
   state->dy=     -((signed char)(data[2]) + (signed char)(data[4]));
   return 0;
}

static int M_mm(Gpm_Event *state,  unsigned char *data)
{
   state->buttons= data[0] & 0x07;
   state->dx=      (data[0] & 0x10) ?   data[1] : - data[1];
   state->dy=      (data[0] & 0x08) ? - data[2] :   data[2];
   return 0;
}

static int M_logi(Gpm_Event *state,  unsigned char *data) /* equal to mm */
{
   state->buttons= data[0] & 0x07;
   state->dx=      (data[0] & 0x10) ?   data[1] : - data[1];
   state->dy=      (data[0] & 0x08) ? - data[2] :   data[2];
   return 0;
}

static int M_bm(Gpm_Event *state,  unsigned char *data) /* equal to sun */
{
   state->buttons= (~data[0]) & 0x07;
   state->dx=      (signed char)data[1];
   state->dy=     -(signed char)data[2];
   return 0;
}

static int M_ps2(Gpm_Event *state,  unsigned char *data)
{
   static int tap_active=0; /* there exist glidepoint ps2 mice */

   state->buttons=
      !!(data[0]&1) * GPM_B_LEFT +
      !!(data[0]&2) * GPM_B_RIGHT +
      !!(data[0]&4) * GPM_B_MIDDLE;

   if (data[0]==0 && (which_mouse->opt_glidepoint_tap)) /* by default this is false */
      state->buttons = tap_active = (which_mouse->opt_glidepoint_tap);
   else if (tap_active) {
      if (data[0]==8)
         state->buttons = tap_active = 0;
      else
         state->buttons = tap_active;
   }  

   /* Some PS/2 mice send reports with negative bit set in data[0]
    * and zero for movement.  I think this is a bug in the mouse, but
    * working around it only causes artifacts when the actual report is -256;
    * they'll be treated as zero. This should be rare if the mouse sampling
    * rate is set to a reasonable value; the default of 100 Hz is plenty.
    * (Stephen Tell)
    */
   if(data[1] != 0)
      state->dx=   (data[0] & 0x10) ? data[1]-256 : data[1];
   else
      state->dx = 0;
   if(data[2] != 0)
      state->dy= -((data[0] & 0x20) ? data[2]-256 : data[2]);
   else
      state->dy = 0;
   return 0;
}

static int M_imps2(Gpm_Event *state,  unsigned char *data)
{
   
   static int tap_active=0;         /* there exist glidepoint ps2 mice */
   state->wdx  = state->wdy   = 0;  /* Clear them.. */
   state->dx   = state->dy    = state->wdx = state->wdy = 0;

   state->buttons= ((data[0] & 1) << 2)   /* left              */
      | ((data[0] & 6) >> 1);             /* middle and right  */
   
   if (data[0]==0 && (which_mouse->opt_glidepoint_tap)) // by default this is false
      state->buttons = tap_active = (which_mouse->opt_glidepoint_tap);
   else if (tap_active) {
      if (data[0]==8)
         state->buttons = tap_active = 0;
      else state->buttons = tap_active;
   }

   /* Standard movement.. */
   state->dx = (data[0] & 0x10) ? data[1] - 256 : data[1];
   state->dy = (data[0] & 0x20) ? -(data[2] - 256) : -data[2];
   
   /* The wheels.. */
   unsigned char wheel = data[3] & 0x0f;
   if (wheel > 0) {
     // use the event type GPM_MOVE rather than GPM_DOWN for wheel movement
     // to avoid single/double/triple click processing:
     switch (wheel) {
       /* rodney 13/mar/2008
        * The use of GPM_B_UP / GPM_B_DOWN is very unclear; 
        *  only mouse type ms3 uses these
        * For this mouse, we only support the relative movement
        * i.e. no button is set (same as mouse movement), wdy changes +/-
        *  according to wheel movement (+ for rolling away from user)
        * wdx (horizontal scroll) is for a second wheel. They do exist! */
        case 0x0f: state->wdy = +1; break;
        case 0x01: state->wdy = -1; break;
        case 0x0e: state->wdx = +1; break;
        case 0x02: state->wdx = -1; break;
     }
   }

   return 0;

}

static int M_netmouse(Gpm_Event *state,  unsigned char *data)
{
   /* Avoid these beasts if you can.  They connect to normal PS/2 port,
    * but their protocol is one byte longer... So if you have notebook
    * (like me) with internal PS/2 mouse, it will not work
    * together. They have four buttons, but two middle buttons can not
    * be pressed simultaneously, and two middle buttons do not send
    * 'up' events (however, they autorepeat...)

    * Still, you might want to run this mouse in plain PS/2 mode -
    * where it behaves correctly except that middle 2 buttons do
    * nothing.

    * Protocol is
    * 3 bytes like normal PS/2
    * 4th byte: 0xff button 'down', 0x01 button 'up'
    * [this is so braindamaged that it *must* be some kind of
    * compatibility glue...]

    * Pavel Machek <pavel@ucw.cz>
    */

   state->buttons=
      !!(data[0]&1) * GPM_B_LEFT +
      !!(data[0]&2) * GPM_B_RIGHT +
      !!(data[3]) * GPM_B_MIDDLE;

   if(data[1] != 0)
      state->dx=   (data[0] & 0x10) ? data[1]-256 : data[1];
   else
      state->dx = 0;
   if(data[2] != 0)
      state->dy= -((data[0] & 0x20) ? data[2]-256 : data[2]);
   else
      state->dy = 0;
   return 0;
}

/* standard ps2 */
static Gpm_Type *I_ps2(int fd, unsigned short flags,
        struct Gpm_Type *type, int argc, char **argv)
{
   static unsigned char s[] = { 246, 230, 244, 243, 100, 232, 3, };
   write (fd, s, sizeof (s));
   usleep (30000);
   tcflush (fd, TCIFLUSH);
   return type;
}

static Gpm_Type *I_netmouse(int fd, unsigned short flags,
         struct Gpm_Type *type, int argc, char **argv)
{
   unsigned char magic[6] = { 0xe8, 0x03, 0xe6, 0xe6, 0xe6, 0xe9 };
   int i;

   if (check_no_argv(argc, argv)) return NULL;
   for (i=0; i<6; i++) {
      unsigned char c = 0;
      write( fd, magic+i, 1 );
      read( fd, &c, 1 );
      if (c != 0xfa) {
         gpm_report(GPM_PR_ERR,GPM_MESS_NETM_NO_ACK,c);
         return NULL;
      }
   }
   {
      unsigned char rep[3] = { 0, 0, 0 };
      read( fd, rep, 1 );
      read( fd, rep+1, 1 );
      read( fd, rep+2, 1 );
      if (rep[0] || (rep[1] != 0x33) || (rep[2] != 0x55)) {
         gpm_report(GPM_PR_ERR,GPM_MESS_NETM_INV_MAGIC, rep[0], rep[1], rep[2]);
         return NULL;
      }
   }
   return type;
}

#define GPM_B_BOTH (GPM_B_LEFT|GPM_B_RIGHT)
static int M_mman(Gpm_Event *state,  unsigned char *data)
{
   /*
    * the damned MouseMan has 3/4 bytes packets. The extra byte 
    * is only there if the middle button is active.
    * I get the extra byte as a packet with magic numbers in it.
    * and then switch to 4-byte mode.
    */
    static unsigned char prev=0;
    static Gpm_Type *mytype=mice; /* it is the first */
    unsigned char b = (*data>>4);

   if(data[1]==GPM_EXTRA_MAGIC_1 && data[2]==GPM_EXTRA_MAGIC_2) {
      /* got unexpected fourth byte */
      if (b > 0x3) return -1;  /* just a sanity check */
      //if ((b=(*data>>4)) > 0x3) return -1;  /* just a sanity check */
      state->dx=state->dy=0;
    
      mytype->packetlen=4;
      mytype->getextra=0;
   }
   else {
      /* got 3/4, as expected */
    
      /* motion is independent of packetlen... */
      state->dx= (signed char)(((data[0] & 0x03) << 6) | (data[1] & 0x3F));
      state->dy= (signed char)(((data[0] & 0x0C) << 4) | (data[2] & 0x3F));
    
      prev= ((data[0] & 0x20) >> 3) | ((data[0] & 0x10) >> 4);
      if (mytype->packetlen==4) b=data[3]>>4;
   }
     
   if(mytype->packetlen==4) {
      if(b == 0) {
         mytype->packetlen=3;
         mytype->getextra=1;
      } else {
         if (b & 0x2) prev |= GPM_B_MIDDLE;
         if (b & 0x1) prev |= (which_mouse->opt_glidepoint_tap);
      }
   }
   state->buttons=prev;

   /* This "chord-middle" behaviour was reported by David A. van Leeuwen */
   if (((prev^state->buttons) & GPM_B_BOTH)==GPM_B_BOTH )
      state->buttons = state->buttons ? GPM_B_MIDDLE : 0;
   prev=state->buttons;
  
   return 0;
}

/* 
 *  Wacom Tablets with pen and mouse:
 *  Relative-Mode And Absolute-Mode; 
 *  Stefan Runkel  01/2000 <runkel@runkeledv.de>,
 *  Mike Pioskowik 01/2000 <pio.d1mp@nbnet.de>
*/
/* for relative and absolute : */
int WacomModell =-1;          /* -1 means "dont know" */
int WacomAbsoluteWanted=0;    /* Tell Driver if Relative or Absolute */
int wmaxx, wmaxy;         
char upmbuf[25]; /* needed only for macro buttons of ultrapad */

/* Data for Wacom Modell Identification */
/* (MaxX, MaxY are for Modells which do not answer resolution requests */
struct WC_MODELL{
   char name[15];       
   char magic[3];
   int  maxX;
   int  maxY;
   int  border;
   int  treshold;
} wcmodell[] = {
   /* ModellName    Magic     MaxX     MaxY  Border  Tresh */
    { "UltraPad"  , "UD",        0,       0,    250,    20 }, 
 /* { "Intuos"    , "GD",        0,       0,      0,    20 }, not supported */
    { "PenPartner", "CT",        0,       0,      0,    20 }, 
    { "Graphire"  , "ET",     5103,    3711,      0,    20 }
   };

#define IsA(m) ((WacomModell==(-1))? 0:!strcmp(#m,wcmodell[WacomModell].name))

static int M_wacom(Gpm_Event *state, unsigned char *data)
{
   static int ox=-1, oy;
   int x, y;
   int macro=0;      /* macro buttons from tablet */

   /* Bit [0]&64 seems to have different meanings -
    *   graphire:   inside of active area;
    *   Ultrapad:   Pen in proximity (means: pen detected)
    *   Penpartner: no idea...
    * this may be of worth sometimes, but for now, we can say, that
    * if the graphire tells us that we are in the active area, we can tell
    * also that the graphire has the pen in proximity.
    */

   if (!(data[0]&64)) { /* Tool not in proximity or out of active area */
   /* handle Ultrapad macro buttons, if we get a packet without
    * proximity bit but with the buttonflag set, we know that we have
    * a macro event */
      if (IsA(UltraPad)) {
         if (data[0]&8) { /* here: macro button has been pressed */
            if (data[3]&8)  macro=(data[6]); 
            if (data[3]&16) macro=(data[6])+12;
            if (data[3]&32) macro=(data[6])+24;  /* rom-version >= 1.3 */

            state->modifiers=macro;
            /* Here we simulate the middle mousebutton */
            /* with ultrapad Eprom Version 1.2 */
            /* WHY IS THE FOLLOWING CODE DISABLE ? FIXME
            gpm_report(GPM_PR_INFO,GPM_MESS_WACOM_MACRO, macro); 
            if (macro==12) state->buttons =  GPM_B_MIDDLE;
            */
         } /* end if macrobutton pressed */
      } /* end if ultrapad */
          
      if (!IsA(UltraPad)){ /* Tool out of active area */ 
         ox=-1; 
         state->buttons=0; 
         state->dx=state->dy=0; 
      }

      return 0; /* nothing more to do so leave */
   } /* end if Tool out of active area*/ 

   x = (((data[0] & 0x3) << 14) +  (data[1] << 7) + data[2]); 
   y = (((data[3] & 0x3) << 14) + (data[4] << 7) + data[5]);
   
   if (WacomAbsoluteWanted) { /* Absolute Mode */
      if (x>wmaxx) x=wmaxx; if (x<0) x=0;
      if (y>wmaxy) y=wmaxy; if (y<0) y=0;
      state->x  = (x * win.ws_col / wmaxx);
      state->y  = (y * win.ws_row / wmaxy);
       
      realposx = (x / wmaxx); /* this two lines come from the summa driver. */
      realposy = (y / wmaxy); /* they seem to be buggy (always give zero).  */

   } else {                                               /* Relative Mode */
      /* Treshold; if greather then treat tool as first time in proximity */
      if( abs(x-ox)>(wmaxx/wcmodell[WacomModell].treshold) 
       || abs(y-oy)>(wmaxy/wcmodell[WacomModell].treshold) ) ox=x; oy=y;

      state->dx= (x-ox) / (wmaxx / win.ws_col / wcmodell[WacomModell].treshold);
      state->dy= (y-oy) / (wmaxy / win.ws_row / wcmodell[WacomModell].treshold);
   }

   ox=x; oy=y;    
   
   state->buttons=                      /* for Ultra-Pad and graphire */
      !!(data[3]&8)  * GPM_B_LEFT  +
      !!(data[3]&16) * GPM_B_RIGHT +
      !!(data[3]&32) * GPM_B_MIDDLE;   /* UD: rom-version >=1.3 */
   return 0;
}

/* Calcomp UltraSlate tablet (John Anderson)
 * modeled after Wacom and NCR drivers (thanks, guys)
 * Note: I don't know how to get the tablet size from the tablet yet, so
 * these defines will have to do for now.
 */

#define CAL_LIMIT_BRK 255

#define CAL_X_MIN 0x40
#define CAL_X_MAX 0x1340
#define CAL_X_SIZE (CAL_X_MAX - CAL_X_MIN)

#define CAL_Y_MIN 0x40
#define CAL_Y_MAX 0xF40
#define CAL_Y_SIZE (CAL_Y_MAX - CAL_Y_MIN)

static int M_calus(Gpm_Event *state, unsigned char *data)
{
   int x, y;

   x = ((data[1] & 0x3F)<<7) | (data[2] & 0x7F);
   y = ((data[4] & 0x1F)<<7) | (data[5] & 0x7F);

   state->buttons = GPM_B_LEFT * ((data[0]>>2) & 1)
     + GPM_B_MIDDLE * ((data[0]>>3) & 1)
     + GPM_B_RIGHT * ((data[0]>>4) & 1);

   state->dx = 0; state->dy = 0;

   state->x = x < CAL_X_MIN ? 0
     : x > CAL_X_MAX ? win.ws_col+1
     : (long)(x-CAL_X_MIN) * (long)(win.ws_col-1) / CAL_X_SIZE+2;

   state->y = y < CAL_Y_MIN ? win.ws_row + 1
     : y > CAL_Y_MAX ? 0
     : (long)(CAL_Y_MAX-y) * (long)win.ws_row / CAL_Y_SIZE + 1;

   realposx = x < CAL_X_MIN ? 0
     : x > CAL_X_MAX ? 16384
     : (long)(x-CAL_X_MIN) * (long)(16382) / CAL_X_SIZE+2;

   realposy = y < CAL_Y_MIN ? 16384
     : y > CAL_Y_MAX ? 0
     : (long)(CAL_Y_MAX-y) * (long)16383 / CAL_Y_SIZE + 1;

   return 0;
}

static int M_calus_rel(Gpm_Event *state, unsigned char *data)
{
   static int ox=-1, oy;
   int x, y;

   x = ((data[1] & 0x3F)<<7) | (data[2] & 0x7F);
   y = ((data[4] & 0x1F)<<7) | (data[5] & 0x7F);

   if (ox==-1 || abs(x-ox)>CAL_LIMIT_BRK || abs(y-oy)>CAL_LIMIT_BRK) {
      ox=x; oy=y;
   }

   state->buttons = GPM_B_LEFT * ((data[0]>>2) & 1)
     + GPM_B_MIDDLE * ((data[0]>>3) & 1)
     + GPM_B_RIGHT * ((data[0]>>4) & 1);

   state->dx = (x-ox)/5; state->dy = (oy-y)/5;
   ox=x; oy=y;
   return 0;
}



/* ncr pen support (Marc Meis) */

#define NCR_LEFT_X     40
#define NCR_RIGHT_X    2000

#define NCR_BOTTOM_Y   25
#define NCR_TOP_Y      1490

#define NCR_DELTA_X    (NCR_RIGHT_X - NCR_LEFT_X)
#define NCR_DELTA_Y    (NCR_TOP_Y - NCR_BOTTOM_Y)

static int M_ncr(Gpm_Event *state,  unsigned char *data)
{
   int x,y;

   state->buttons= (data[0]&1)*GPM_B_LEFT +
                !!(data[0]&2)*GPM_B_RIGHT;

   state->dx = (signed char)data[1]; /* currently unused */
   state->dy = (signed char)data[2];

   x = ((int)data[3] << 8) + (int)data[4];
   y = ((int)data[5] << 8) + (int)data[6];

   /* these formulaes may look curious, but this is the way it works!!! */

   state->x = x < NCR_LEFT_X
             ? 0
             : x > NCR_RIGHT_X
               ? win.ws_col+1
               : (long)(x-NCR_LEFT_X) * (long)(win.ws_col-1) / NCR_DELTA_X+2;

   state->y = y < NCR_BOTTOM_Y
             ? win.ws_row + 1
             : y > NCR_TOP_Y
          ? 0
          : (long)(NCR_TOP_Y-y) * (long)win.ws_row / NCR_DELTA_Y + 1;

   realposx = x < NCR_LEFT_X
             ? 0
             : x > NCR_RIGHT_X
               ? 16384
               : (long)(x-NCR_LEFT_X) * (long)(16382) / NCR_DELTA_X+2;

   realposy = y < NCR_BOTTOM_Y
             ? 16384
             : y > NCR_TOP_Y
          ? 0
          : (long)(NCR_TOP_Y-y) * (long)16383 / NCR_DELTA_Y + 1;

   return 0;
}

static int M_twid(Gpm_Event *state,  unsigned char *data)
{
   unsigned long message=0UL; int i,h,v;
   static int lasth, lastv, lastkey, key, lock=0, autorepeat=0;

   /* build the message as a single number */
   for (i=0; i<5; i++)
      message |= (data[i]&0x7f)<<(i*7);
   key = message & TW_ANY_KEY;

   if ((message & TW_MOD_M) == 0) { /* manage keyboard */
      if (((message & TW_ANY_KEY) != lastkey) || autorepeat)
         autorepeat = twiddler_key(message);
      lastkey = key;
      lock = 0; return -1; /* no useful mouse data */
   } 

   switch (message & TW_ANY1) {
      case TW_L1: state->buttons = GPM_B_RIGHT;  break;
      case TW_M1: state->buttons = GPM_B_MIDDLE; break;
      case TW_R1: state->buttons = GPM_B_LEFT;   break;
      case     0: state->buttons = 0;            break;
    }
   /* also, allow R1 R2 R3 (or L1 L2 L3) to be used as mouse buttons */
   if (message & TW_ANY2)  state->buttons |= GPM_B_MIDDLE;
   if (message & TW_L3)    state->buttons |= GPM_B_LEFT;
   if (message & TW_R3)    state->buttons |= GPM_B_RIGHT;

   /* put in modifiers information */
   {
      struct {unsigned long in, out;} *ptr, list[] = {
         { TW_MOD_S,  1<<KG_SHIFT },
         { TW_MOD_C,  1<<KG_CTRL  },
         { TW_MOD_A,  1<<KG_ALT   },
         { 0,         0}
      };
      for (ptr = list; ptr->in; ptr++)
         if(message & ptr->in) state->modifiers |= ptr->out;
   }

   /* now extraxt H/V */
   h = (message >> TW_H_SHIFT) & TW_M_MASK;
   v = (message >> TW_V_SHIFT) & TW_M_MASK;
   if (h & TW_M_BIT) h = -(TW_M_MASK + 1 - h);
   if (v & TW_M_BIT) v = -(TW_M_MASK + 1 - v);
  
#ifdef TWIDDLER_STATIC
   /* static implementation: return movement */
   if (!lock) {
      lasth = h;
      lastv = v;
      lock = 1;
   } 
   state->dx = -(h-lasth); lasth = h;
   state->dy = -(v-lastv); lastv = v; 

#elif defined(TWIDDLER_BALLISTIC)
   {
      /* in case I'll change the resolution */
      static int tw_threshold = 5;  /* above this it moves */
      static int tw_scale = 5;      /* every 5 report one */

      if (h > -tw_threshold && h < tw_threshold) state->dx=0;
      else {
         h = h - (h<0)*tw_threshold +lasth;
         lasth = h%tw_scale;
         state->dx = -(h/tw_scale);
      }
      if (v > -tw_threshold && v < tw_threshold) state->dy=0;
      else {
         v = v - (v<0)*tw_threshold +lastv;
         lastv = v%tw_scale;
         state->dy = -(v/tw_scale);
      }
   }

#else /* none defined: use mixed approach */
   {
      /* in case I'll change the resolution */
      static int tw_threshold = 60;    /* above this, movement is ballistic */
      static int tw_scale = 10;         /* ball: every 6 units move one unit */
      static int tw_static_scale = 3;  /* stat: every 3 units move one unit */
      static int lasthrest, lastvrest; /* integral of small motions uses rest */

      if (!lock) {
         lasth = h; lasthrest = 0;
         lastv = v; lastvrest = 0;
         lock = 1;
      } 

      if (h > -tw_threshold && h < tw_threshold) {
         state->dx = -(h-lasth+lasthrest)/tw_static_scale;
         lasthrest =  (h-lasth+lasthrest)%tw_static_scale;
      } else /* ballistic */ {
         h = h - (h<0)*tw_threshold + lasthrest;
         lasthrest = h%tw_scale;
         state->dx = -(h/tw_scale);
      }
      lasth = h;
      if (v > -tw_threshold && v < tw_threshold) {
         state->dy = -(v-lastv+lastvrest)/tw_static_scale;
         lastvrest =  (v-lastv+lastvrest)%tw_static_scale;
      } else /* ballistic */ {
         v = v - (v<0)*tw_threshold + lastvrest;
         lastvrest = v%tw_scale;
         state->dy = -(v/tw_scale);
      }
      lastv = v;
   }
#endif

   /* fprintf(stderr,"%4i %4i -> %3i %3i\n",h,v,state->dx,state->dy); */
   return 0;
}

#ifdef HAVE_LINUX_JOYSTICK_H
/* Joystick mouse emulation (David Given) */

static int M_js(Gpm_Event *state,  unsigned char *data)
{
   struct JS_DATA_TYPE *jdata = (void*)data;
   static int centerx = 0;
   static int centery = 0;
   static int oldbuttons = 0;
   static int count = 0;
   int dx;
   int dy;

   count++;
   if (count < 200) {
      state->buttons = oldbuttons;
      state->dx = 0;
      state->dy = 0;
      return 0;
   }
   count = 0;

   if (centerx == 0) {
      centerx = jdata->x;
      centery = jdata->y;
   }

   state->buttons = ((jdata->buttons & 1) * GPM_B_LEFT) |
                    ((jdata->buttons & 2) * GPM_B_RIGHT);
   oldbuttons     = state->buttons;

   dx             = (jdata->x - centerx) >> 6;
   dy             = (jdata->y - centery) >> 6;

   if (dx > 0) state->dx =   dx * dx;
   else        state->dx = -(dx * dx);
   state->dx >>= 2;

   if (dy > 0) state->dy = dy * dy;
   else        state->dy = -(dy * dy);
   state->dy >>= 2;

   /* Prevent pointer drift. (PC joysticks are notoriously inaccurate.) */

   if ((state->dx >= -1) && (state->dx <= 1)) state->dx = 0;
   if ((state->dy >= -1) && (state->dy <= 1)) state->dy = 0;

   return 0;
}
#endif /* have joystick.h */

/* Synaptics TouchPad mouse emulation (Henry Davies) */
static int M_synaptics_serial(Gpm_Event *state,  unsigned char *data)
{
   syn_process_serial_data (state, data);
   return 0;
}


/* Synaptics TouchPad mouse emulation (Henry Davies) */
static int M_synaptics_ps2(Gpm_Event *state,  unsigned char *data)
{
   syn_process_ps2_data(state, data);
   return 0;
}

static int M_mtouch(Gpm_Event *state,  unsigned char *data)
{
   /*
    * This is a simple decoder for the MicroTouch touch screen
    * devices. It uses the "tablet" format and only generates button-1
    * events. Check README.microtouch for additional information.
    */
   int x, y;
   static int avgx=-1, avgy;       /* average over time, for smooth feeling */
   static int upx, upy;            /* keep track of last finger-up place */
   static struct timeval uptv, tv; /* time of last up, and down events */

   #define REAL_TO_XCELL(x) (x * win.ws_col / 0x3FFF)
   #define REAL_TO_YCELL(y) (y * win.ws_row / 0x3FFF)

   #define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
   #define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \
                           (t2.tv_usec-t1.tv_usec)/1000)

   if (!(data[0]&0x40)) {
    /*
     * finger-up event: this is usually offset a few pixels,
     * so ignore this x and y values. And invalidate avg.
     */
      upx = avgx;
      upy = avgy;
      GET_TIME(uptv); /* ready for the next finger-down */
      tv.tv_sec = 0;
      state->buttons = 0;
      avgx=-1; /* invalidate avg */
      return 0;
   }

   x =           data[1] | (data[2]<<7);
   y = 0x3FFF - (data[3] | (data[4]<<7));

   if (avgx < 0) { /* press event */
      GET_TIME(tv);
      if (DIF_TIME(uptv, tv) < (which_mouse->opt_time)) {
         /* count as button press placed at finger-up pixel */
         state->buttons = GPM_B_LEFT;
         realposx = avgx = upx; state->x = REAL_TO_XCELL(realposx);
         realposy = avgy = upy; state->y = REAL_TO_YCELL(realposy);
         upx = (upx - x); /* upx and upy become offsets to use for this drag */
         upy = (upy - y);
         return 0;
      }
      /* else, count as a new motion event */
      tv.tv_sec = 0; /* invalidate */
      realposx = avgx = x; state->x = REAL_TO_XCELL(realposx);
      realposy = avgy = y; state->y = REAL_TO_YCELL(realposy);
   }

   state->buttons = 0;
   if (tv.tv_sec) { /* a drag event: use position relative to press */
      x += upx;
      y += upy;
      state->buttons = GPM_B_LEFT;
   }

   realposx = avgx = (9*avgx + x)/10; state->x = REAL_TO_XCELL(realposx);
   realposy = avgy = (9*avgy + y)/10; state->y = REAL_TO_YCELL(realposy);

   return 0;

   #undef REAL_TO_XCELL
   #undef REAL_TO_YCELL
   #undef GET_TIME
   #undef DIF_TIME
}

/*
 * This decoder is copied and adapted from the above mtouch.
 * However, this one uses argv, which should be ported above as well.
 * These variables are used for option passing
 */
static int gunze_avg = 9; /* the bigger the smoother */
static int gunze_calib[4]; /* x0,y0 x1,y1 (measured at 1/8 and 7/8) */
static int gunze_debounce = 100; /* milliseconds: ignore shorter taps */

static int M_gunze(Gpm_Event *state,  unsigned char *data)
{
   /*
    * This generates button-1 events, by now.
    * Check README.gunze for additional information.
    */
   int x, y;
   static int avgx, avgy;          /* average over time, for smooth feeling */
   static int upx, upy;            /* keep track of last finger-up place */
   static int released = 0, dragging = 0;
   static struct timeval uptv, tv; /* time of last up, and down events */
   int timediff;
    
   #define REAL_TO_XCELL(x) (x * win.ws_col / 0x3FFF)
   #define REAL_TO_YCELL(y) (y * win.ws_row / 0x3FFF)
    
   #define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
   #define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \
                           (t2.tv_usec-t1.tv_usec)/1000)

   if (data[0] == 'R') {
      /*
       * finger-up event: this is usually offset a few pixels,
       * so ignore this x and y values. And invalidate avg.
       */
      upx = avgx;
      upy = avgy;
      GET_TIME(uptv); /* ready for the next finger-down */
      state->buttons = 0;
      released=1;
      return 0;
   }

   if(sscanf(data+1, "%d,%d", &x, &y)!= 2) {
      gpm_report(GPM_PR_INFO,GPM_MESS_GUNZE_INV_PACK,data);
      return 10; /* eat one byte only, leave ten of them */
   }
    /*
     * First of all, calibrate decoded data;
     * the four numbers are 1/8X 1/8Y, 7/8X, 7/8Y
     */
   x = 128 + 768 * (x - gunze_calib[0])/(gunze_calib[2]-gunze_calib[0]);
   y = 128 + 768 * (y - gunze_calib[1])/(gunze_calib[3]-gunze_calib[1]);

   /* range is 0-1023, rescale it upwards (0-REALPOS_MAX) */
   x = x * REALPOS_MAX / 1023;
   y = REALPOS_MAX - y * REALPOS_MAX / 1023;

   if (x<0) x = 0; if (x > REALPOS_MAX) x = REALPOS_MAX;
   if (y<0) y = 0; if (y > REALPOS_MAX) y = REALPOS_MAX;

    /*
     * there are some issues wrt press events: since the device needs a
     * non-negligible pressure to detect stuff, it sometimes reports
     * quick up-down sequences, that actually are just the result of a
     * little bouncing of the finger. Therefore, we should discard any
     * release-press pair. This can be accomplished at press time.
     */

   if (released) { /* press event -- or just bounce*/
      GET_TIME(tv);
      timediff = DIF_TIME(uptv, tv);
      released = 0;
      if (timediff > gunze_debounce && timediff < (which_mouse->opt_time)) {
         /* count as button press placed at finger-up pixel */
         dragging = 1;
         state->buttons = GPM_B_LEFT;
         realposx = avgx = upx; state->x = REAL_TO_XCELL(realposx);
         realposy = avgy = upy; state->y = REAL_TO_YCELL(realposy);
         upx = (upx - x); /* upx-upy become offsets to use for this drag */
         upy = (upy - y);
         return 0;
      }
      if (timediff <= gunze_debounce) {
         /* just a bounce, invalidate offset, leave dragging alone */
         upx = upy = 0;
      } else {
         /* else, count as a new motion event, reset avg */
         dragging = 0;
         realposx = avgx = x;
         realposy = avgy = y;
      }
   }

   state->buttons = 0;
   if (dragging) { /* a drag event: use position relative to press */
      x += upx;
      y += upy;
      state->buttons = GPM_B_LEFT;
   }

   realposx = avgx = (gunze_avg * avgx + x)/(gunze_avg+1);
   state->x = REAL_TO_XCELL(realposx);
   realposy = avgy = (gunze_avg * avgy + y)/(gunze_avg+1);
   state->y = REAL_TO_YCELL(realposy);

   return 0;

   #undef REAL_TO_XCELL
   #undef REAL_TO_YCELL
   #undef GET_TIME
   #undef DIF_TIME
}

/*
 * This decoder is copied and adapted from the above mtouch.
 */
static int elo_click_ontouch = 0; /* the bigger the smoother */
static int M_etouch(Gpm_Event *state,  unsigned char *data)
{ /*
   * This is a simple decoder for the EloTouch touch screen devices. 
   * ELO format SmartSet UTsXXYYZZc 9600,N,8,1
   * c=checksum = 0xAA+'T'+'U'+s+X+X+Y+Y+Z+Z (XXmax=YYmax=0x0FFF=4095)
   * s=status   bit 0=init touch  1=stream touch  2=release
   */
#define ELO_CLICK_ONTOUCH	/* ifdef then ButtonPress on first Touch 
				         else first Move then Touch*/
  int x, y;
  static int avgx=-1, avgy;       /* average over time, for smooth feeling */
  static int upx, upy;            /* keep track of last finger-up place */
  static struct timeval uptv, tv; /* time of last up, and down events */

  #define REAL_TO_XCELL(x) (x * win.ws_col / 0x3FFF)
  #define REAL_TO_YCELL(y) (y * win.ws_row / 0x3FFF)

  #define GET_TIME(tv) (gettimeofday(&tv, (struct timezone *)NULL))
  #define DIF_TIME(t1,t2) ((t2.tv_sec -t1.tv_sec) *1000+ \
                           (t2.tv_usec-t1.tv_usec)/1000)

  if (data[2]&0x04)	/* FINGER UP - Release */
  { upx = avgx;		/* ignore this x, y */
    upy = avgy;		/* store Finger UP possition */
    GET_TIME(uptv);	/* set time for the next finger-down */
    tv.tv_sec = 0;	/* NO DRAG */
    avgx=-1;		/* FINGER IS UP */
    state->buttons = 0;
    return 0;
  }

  /* NOW WE HAVe FINGER DOWN */
  x = data[3] | (data[4]<<8); x&=0xfff;
  y = data[5] | (data[6]<<8); x&=0xfff;
  x = REALPOS_MAX * (x - gunze_calib[0])/(gunze_calib[2]-gunze_calib[0]);
  y = REALPOS_MAX * (y - gunze_calib[1])/(gunze_calib[3]-gunze_calib[1]);
  if (x<0) x = 0; if (x > REALPOS_MAX) x = REALPOS_MAX;
  if (y<0) y = 0; if (y > REALPOS_MAX) y = REALPOS_MAX;

  if (avgx < 0)		/* INITIAL TOUCH, FINGER WAS UP */
  { GET_TIME(tv);
    state->buttons = 0;
    if (DIF_TIME(uptv, tv) < (which_mouse->opt_time))
    {	/* if Initial Touch immediate after finger UP then start DRAG */
	x=upx; y=upy;  /* A:start DRAG at finger-UP position */ 
	if (elo_click_ontouch==0)	state->buttons = GPM_B_LEFT;
    }
    else /*  1:MOVE to Initial Touch position */
    {	upx=x; upy=y; /* store position of Initial Touch into upx, upy */
	if (elo_click_ontouch==0)	tv.tv_sec=0; /* no DRAG */
    }
    realposx = avgx = x; state->x = REAL_TO_XCELL(realposx);
    realposy = avgy = y; state->y = REAL_TO_YCELL(realposy);
    return 0;
  } /* endof INITIAL TOUCH */


  state->buttons = 0; /* Motion event */
  if (tv.tv_sec)	/* draging or elo_click_ontouch */
  { state->buttons = GPM_B_LEFT;
    if (elo_click_ontouch) 
    {	x=avgx=upx;	/* 2:BUTTON PRESS at Initial Touch position */
	y=avgy=upy;
	tv.tv_sec=0;   /* so next time 3:MOVE again until Finger UP*/
    } /* else B:continue DRAG to current possition */
  }

  realposx = avgx = (9*avgx + x)/10; state->x = REAL_TO_XCELL(realposx);
  realposy = avgy = (9*avgy + y)/10; state->y = REAL_TO_YCELL(realposy);
  return 0;

  #undef REAL_TO_XCELL
  #undef REAL_TO_YCELL
  #undef GET_TIME
  #undef DIF_TIME
}


/* Support for DEC VSXXX-AA and VSXXX-GA serial mice used on   */
/* DECstation 5000/xxx, DEC 3000 AXP and VAXstation 4000       */
/* workstations                                                */
/* written 2001/07/11 by Karsten Merker (merker@linuxtag.org)  */
/* modified (completion of the protocol specification and      */
/* corresponding correction of the protocol identification     */
/* mask) 2001/07/12 by Maciej W. Rozycki (macro@ds2.pg.gda.pl) */

static int M_vsxxx_aa(Gpm_Event *state, unsigned char *data)
{

/* The mouse protocol is as follows:
 * 4800 bits per second, 8 data bits, 1 stop bit, odd parity
 * 3 data bytes per data packet:
 *              7     6     5     4     3     2     1     0
 * First Byte:  1     0     0   SignX SignY  LMB   MMB   RMB
 * Second Byte  0     DX    DX    DX    DX    DX    DX    DX
 * Third Byte   0     DY    DY    DY    DY    DY    DY    DY
 *
 * SignX:     sign bit for X-movement
 * SignY:     sign bit for Y-movement
 * DX and DY: 7-bit-absolute values for delta-X and delta-Y, sign extensions
 *            are in SignX resp. SignY.
 *
 * There are a few commands the mouse accepts:
 * "D" selects the prompt mode,
 * "P" requests a mouse's position (also selects the prompt mode),
 * "R" selects the incremental stream mode,
 * "T" performs a self test and identification (power-up-like),
 * "Z" performs undocumented test functions (a byte follows).
 * Parity as well as bit #7 of commands are ignored by the mouse.
 *
 * 4 data bytes per self test packet (useful for hot-plug):
 *              7     6     5     4     3     2     1     0
 * First Byte:  1     0     1     0     R3    R2    R1    R0
 * Second Byte  0     M2    M1    M0    0     0     1     0
 * Third Byte   0     E6    E5    E4    E3    E2    E1    E0
 * Fourth Byte  0     0     0     0     0    LMB   MMB   RMB
 *
 * R3-R0:     revision,
 * M2-M0:     manufacturer location code,
 * E6-E0:     error code:
 *            0x00-0x1f: no error (fourth byte is button state),
 *            0x3d:      button error (fourth byte specifies which),
 *            else:      other error.
 * 
 * The mouse powers up in the prompt mode but we use the stream mode.
 */

  state->buttons = data[0]&0x07;
  state->dx = (data[0]&0x10) ? data[1] : -data[1];
  state->dy = (data[0]&0x08) ? -data[2] : data[2];
  return 0;
}

/*  Genius Wizardpad tablet  --  Matt Kimball (mkimball@xmission.com)  */
static int wizardpad_width = -1;
static int wizardpad_height = -1;
static int M_wp(Gpm_Event *state,  unsigned char *data)
{
   int x, y, pressure;

   x = ((data[4] & 0x1f) << 12) | ((data[3] & 0x3f) << 6) | (data[2] & 0x3f);
   state->x = x * win.ws_col / (wizardpad_width * 40);
   realposx = x * 16383 / (wizardpad_width * 40);

   y = ((data[7] & 0x1f) << 12) | ((data[6] & 0x3f) << 6) | (data[5] & 0x3f);
   state->y = win.ws_row - y * win.ws_row / (wizardpad_height * 40) - 1;
   realposy = 16383 - y * 16383 / (wizardpad_height * 40) - 1;  

   pressure = ((data[9] & 0x0f) << 4) | (data[8] & 0x0f);

   state->buttons=
      (pressure >= 0x20) * GPM_B_LEFT +
      !!(data[1] & 0x02) * GPM_B_RIGHT +
      /* the 0x08 bit seems to catch either of the extra buttons... */
      !!(data[1] & 0x08) * GPM_B_MIDDLE;

   return 0;
}

/*========================================================================*/
/* Then, mice should be initialized */

static Gpm_Type* I_empty(int fd, unsigned short flags,
    struct Gpm_Type *type, int argc, char **argv)
{
    if (check_no_argv(argc, argv)) return NULL;
    return type;
}

static int setspeed(int fd,int old,int new,int needtowrite,unsigned short flags)
{
   struct termios tty;
   char *c;

   tcgetattr(fd, &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 19200: tty.c_cflag = flags | B19200; break;
      case  9600: tty.c_cflag = flags |  B9600; break;
      case  4800: tty.c_cflag = flags |  B4800; break;
      case  2400: tty.c_cflag = flags |  B2400; break;
      case  1200:
      default:    tty.c_cflag = flags | B1200; break;
   }

   tcsetattr(fd, TCSAFLUSH, &tty);

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

   if (needtowrite) write(fd, c, 2);
   usleep(100000);
   tcsetattr(fd, TCSAFLUSH, &tty);
   return 0;
}



static struct {
   int sample; char code[2];
} sampletab[]={
    {  0,"O"},
    { 15,"J"},
    { 27,"K"},
    { 42,"L"},
    { 60,"R"},
    { 85,"M"},
    {125,"Q"},
    {1E9,"N"}, };

static Gpm_Type* I_serial(int fd, unsigned short flags,
    struct Gpm_Type *type, int argc, char **argv)
{
   int i; unsigned char c;
   fd_set set; struct timeval timeout={0,0}; /* used when not debugging */

   /* accept "-o dtr", "-o rts" and "-o both" */
   if (option_modem_lines(fd, argc, argv)) return NULL;

#ifndef DEBUG
   /* flush any pending input (thanks, Miguel) */
   FD_ZERO(&set);
   for(i=0; /* always */ ; i++) {
      FD_SET(fd,&set);
      switch(select(fd+1,&set,(fd_set *)NULL,(fd_set *)NULL,&timeout/*zero*/)){
         case  1: if (read(fd,&c,1)==0) break;
         case -1: continue;
      }
      break;
   }

   if (type->fun==M_logimsc) write(fd, "QU", 2 );

#if 0 /* Did this ever work? -- I don't know, but should we not remove it,
       * if it doesn't work ??? -- Nico */
   if (type->fun==M_ms && i==2 && c==0x33)  { /* Aha.. a mouseman... */
      gpm_report(GPM_PR_INFO,GPM_MESS_MMAN_DETECTED);
      return mice; /* it is the first */
   }
#endif
#endif

   /* Non mman: change from any available speed to the chosen one */
   for (i=9600; i>=1200; i/=2)
      setspeed(fd, i, (which_mouse->opt_baud), (type->fun != M_mman) /* write */, flags);

   /*
    * reset the MouseMan/TrackMan to use the 3/4 byte protocol
    * (Stephen Lee, sl14@crux1.cit.cornell.edu)
    * Changed after 1.14; why not having "I_mman" now?
    */
   if (type->fun==M_mman) {
      setspeed(fd, 1200, 1200, 0, flags); /* no write */
      write(fd, "*X", 2);
      setspeed(fd, 1200, (which_mouse->opt_baud), 0, flags); /* no write */
      return type;
   }

   if(type->fun==M_geni) {
      gpm_report(GPM_PR_INFO,GPM_MESS_INIT_GENI);
      setspeed(fd, 1200, 9600, 1, flags); /* write */
      write(fd, ":" ,1); 
      write(fd, "E" ,1); /* setup tablet. relative mode, resolution... */
      write(fd, "@" ,1); /* setup tablet. relative mode, resolution... */
    }

   if (type->fun==M_synaptics_serial) {
      int packet_length;

      setspeed (fd, 1200, 1200, 1, flags);
      packet_length = syn_serial_init (fd);
      setspeed (fd, 1200, 9600, 1, flags);

      type->packetlen = packet_length;
      type->howmany   = packet_length;
    }

   if (type->fun==M_vsxxx_aa) {
      setspeed (fd, 4800, 4800, 0, flags); /* no write */
      write(fd, "R", 1); /* initialize a mouse; without getting an "R" */
                         /* a mouse does not send a bytestream         */
   }

   return type;
}

static Gpm_Type* I_logi(int fd, unsigned short flags,
       struct Gpm_Type *type, int argc, char **argv)
{
   int i;
   struct stat buf;
   int busmouse;

   if (check_no_argv(argc, argv)) return NULL;

   /* is this a serial- or a bus- mouse? */
   if(fstat(fd,&buf)==-1) gpm_report(GPM_PR_OOPS,GPM_MESS_FSTAT);
   i=MAJOR(buf.st_rdev);
   
   /* I don't know why this is herein, but I remove it. I don't think a 
    * hardcoded ttyname in a C file is senseful. I think if the device 
    * exists must be clear before. Not here. */
   /********* if (stat("/dev/ttyS0",&buf)==-1) gpm_oops("stat()"); *****/
   busmouse=(i != MAJOR(buf.st_rdev));

   /* fix the howmany field, so that serial mice have 1, while busmice have 3 */
   type->howmany = busmouse ? 3 : 1;

   /* change from any available speed to the chosen one */
   for (i=9600; i>=1200; i/=2) setspeed(fd, i, (which_mouse->opt_baud), 1 /* write */, flags);

   /* this stuff is peculiar of logitech mice, also for the serial ones */
   write(fd, "S", 1);
   setspeed(fd, (which_mouse->opt_baud), (which_mouse->opt_baud), 1 /* write */,
      CS8 |PARENB |PARODD |CREAD |CLOCAL |HUPCL);

   /* configure the sample rate */
   for (i=0;(which_mouse->opt_sample)<=sampletab[i].sample;i++) ;
   write(fd,sampletab[i].code,1);
   return type;
}

static Gpm_Type *I_wacom(int fd, unsigned short flags,
                         struct Gpm_Type *type, int argc, char **argv)
{
/* wacom graphire tablet */
#define UD_RESETBAUD     "\r$"      /* reset baud rate to default (wacom V) */
                                    /* or switch to wacom IIs (wacomIV)     */ 
#define UD_RESET         "#\r"      /* Reset tablet and enable WACOM IV     */
#define UD_SENDCOORDS    "ST\r"     /* Start sending coordinates            */
#define UD_FIRMID        "~#\r"     /* Request firmware ID string           */
#define UD_COORD         "~C\r"     /* Request max coordinates              */
#define UD_STOP          "\nSP\r"   /* stop sending coordinates             */

   void reset_wacom()
   { 
      /* Init Wacom communication; this is modified from xf86Wacom.so module */
      /* Set speed to 19200 */
      setspeed (fd, 1200, 19200, 0, B19200|CS8|CREAD|CLOCAL|HUPCL);
      /* Send Reset Baudrate Command */ 
      write(fd, UD_RESETBAUD, strlen(UD_RESETBAUD));
      usleep(250000);   
      /* Send Reset Command */
      write(fd, UD_RESET,     strlen(UD_RESET));
      usleep(75000);
      /* Set speed to 9600bps */
      setspeed (fd, 1200, 9600, 0, B9600|CS8|CREAD|CLOCAL|HUPCL);
      /* Send Reset Command */
      write(fd, UD_RESET, strlen(UD_RESET));
      usleep(250000);  
      write(fd, UD_STOP, strlen(UD_STOP));
      usleep(100000);
   }

   int wait_wacom()
   {
      /* 
       *  Wait up to 200 ms for Data from Tablet. 
       *  Do not read that data.
       *  Give back 0 on timeout condition, -1 on error and 1 for DataPresent
       */
      struct timeval timeout;
      fd_set readfds;
      int err;
      FD_ZERO(&readfds);  FD_SET(fd, &readfds);
      timeout.tv_sec = 0; timeout.tv_usec = 200000;
      err = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
      return((err>0)?1:err);
   }

   char buffer[50], *p;

   int RequestData(char *cmd)
   {
      int err;
      /* 
       * Send cmd if not null, and get back answer from tablet. 
       * Get Data to buffer until full or timeout.
       * Give back 0 for timeout and !0 for buffer full
       */
      if (cmd) write(fd,cmd,strlen(cmd));
      memset(buffer,0,sizeof(buffer)); p=buffer;
      err=wait_wacom();
      while (err != -1 && err && (p-buffer)<(sizeof(buffer)-1)) {
         p+= read(fd,p,(sizeof(buffer)-1)-(p-buffer));
         err=wait_wacom();
      }
      /* return 1 for buffer full */
      return ((strlen(buffer) >= (sizeof(buffer)-1))? !0 :0);
   }

   /*
    * We do both modes, relative and absolute, with the same function.
    * If WacomAbsoluteWanted is !0 then that function calculates
    * absolute values, else relative ones.
    * We set this by the -o switch:
    * gpm -t wacom -o absolute   # does absolute mode
    * gpm -t wacom               # does relative mode
    * gpm -t wacom -o relative   # does relative mode
    */

   /* accept boolean options absolute and relative */
   static argv_helper optioninfo[] = {
         {"absolute",  ARGV_BOOL, u: {iptr: &WacomAbsoluteWanted}, value: !0},
         {"relative",  ARGV_BOOL, u: {iptr: &WacomAbsoluteWanted}, value:  0},
         {"",       ARGV_END} 
   };
   parse_argv(optioninfo, argc, argv); 
   type->absolute = WacomAbsoluteWanted;
   reset_wacom();

   /* "Flush" input queque */
   while(RequestData(NULL)) ;

   /* read WACOM-ID */
   RequestData(UD_FIRMID); 

   /* Search for matching modell */
   for(WacomModell=0;
      WacomModell< (sizeof(wcmodell) / sizeof(struct WC_MODELL));
      WacomModell++ ) {
      if (!strncmp(buffer+2,wcmodell[WacomModell].magic, 2)) {
         /* Magic matches, modell found */
         wmaxx=wcmodell[WacomModell].maxX;
         wmaxy=wcmodell[WacomModell].maxY;
         break;
      }
   }
   if(WacomModell >= (sizeof(wcmodell) / sizeof(struct WC_MODELL))) 
      WacomModell=-1;
   gpm_report(GPM_PR_INFO,GPM_MESS_WACOM_MOD, type->absolute? 'A':'R',
                (WacomModell==(-1))? "Unknown" : wcmodell[WacomModell].name,
                buffer+2);
   
   /* read Wacom max size */
   if(WacomModell!=(-1) && (!wcmodell[WacomModell].maxX)) {
      RequestData(UD_COORD);
      sscanf(buffer+2, "%d,%d", &wmaxx, &wmaxy);
      wmaxx = (wmaxx-wcmodell[WacomModell].border);
      wmaxy = (wmaxy-wcmodell[WacomModell].border);
   }
   write(fd,UD_SENDCOORDS,4);

   return type;
}

static Gpm_Type *I_pnp(int fd, unsigned short flags,
             struct Gpm_Type *type, int argc, char **argv)
{  
   struct termios tty;

   /* accept "-o dtr", "-o rts" and "-o both" */
   if (option_modem_lines(fd, argc, argv)) return NULL;

   /*
    * Just put the device to 1200 baud. Thanks to Francois Chastrette
    * for his great help and debugging with his own pnp device.
    */
   tcgetattr(fd, &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;
   tty.c_cflag = flags | B1200;
   tcsetattr(fd, TCSAFLUSH, &tty); /* set parameters */

   /*
    * Don't read the silly initialization string. I don't want to see
    * the vendor name: it is only propaganda, with no information.
    */
   
   return type;
}

/*
 * Sends the SEND_ID command to the ps2-type mouse.
 * Return one of GPM_AUX_ID_...
 */
static int read_mouse_id(int fd)
{
   unsigned char c = GPM_AUX_SEND_ID;
   unsigned char id;

   write(fd, &c, 1);
   read(fd, &c, 1);
   if (c != GPM_AUX_ACK) {
      return(GPM_AUX_ID_ERROR);
   }
   read(fd, &id, 1);

   return(id);
}

/**
 * Writes the given data to the ps2-type mouse.
 * Checks for an ACK from each byte.
 * 
 * Returns 0 if OK, or >0 if 1 or more errors occurred.
 */
static int write_to_mouse(int fd, unsigned char *data, size_t len)
{
   int i;
   int error = 0;
   for (i = 0; i < len; i++) {
      unsigned char c;
      write(fd, &data[i], 1);
      read(fd, &c, 1);
      if (c != GPM_AUX_ACK) error++;
   }

   /* flush any left-over input */
   usleep (30000);
   tcflush (fd, TCIFLUSH);
   return(error);
}


/* intellimouse, ps2 version: Ben Pfaff and Colin Plumb */
/* Autodetect: Steve Bennett */
static Gpm_Type *I_imps2(int fd, unsigned short flags, struct Gpm_Type *type,
                                                       int argc, char **argv)
{
   int id;
   static unsigned char basic_init[] = { GPM_AUX_ENABLE_DEV, GPM_AUX_SET_SAMPLE, 100 };
   static unsigned char imps2_init[] = { GPM_AUX_SET_SAMPLE, 200, GPM_AUX_SET_SAMPLE, 100, GPM_AUX_SET_SAMPLE, 80, };
   static unsigned char ps2_init[] = { GPM_AUX_SET_SCALE11, GPM_AUX_ENABLE_DEV, GPM_AUX_SET_SAMPLE, 100, GPM_AUX_SET_RES, 3, };

   /* Do a basic init in case the mouse is confused */
   write_to_mouse(fd, basic_init, sizeof (basic_init));

   /* Now try again and make sure we have a PS/2 mouse */
   if (write_to_mouse(fd, basic_init, sizeof (basic_init)) != 0) {
      gpm_report(GPM_PR_ERR,GPM_MESS_IMPS2_INIT);
      return(NULL);
   }

   /* Try to switch to 3 button mode */
   if (write_to_mouse(fd, imps2_init, sizeof (imps2_init)) != 0) {
      gpm_report(GPM_PR_ERR,GPM_MESS_IMPS2_FAILED);
      return(NULL);
   }

   /* Read the mouse id */
   id = read_mouse_id(fd);
   if (id == GPM_AUX_ID_ERROR) {
      gpm_report(GPM_PR_ERR,GPM_MESS_IMPS2_MID_FAIL);
      id = GPM_AUX_ID_PS2;
   }

   /* And do the real initialisation */
   if (write_to_mouse(fd, ps2_init, sizeof (ps2_init)) != 0) {
      gpm_report(GPM_PR_ERR,GPM_MESS_IMPS2_SETUP_FAIL);
   }

   if (id == GPM_AUX_ID_IMPS2) {
   /* Really an intellipoint, so initialise 3 button mode (4 byte packets) */
      gpm_report(GPM_PR_INFO,GPM_MESS_IMPS2_AUTO);
      return type;
   }
   if (id != GPM_AUX_ID_PS2) {
      gpm_report(GPM_PR_ERR,GPM_MESS_IMPS2_BAD_ID, id);
   }
   else gpm_report(GPM_PR_INFO,GPM_MESS_IMPS2_PS2);

   for (type=mice; type->fun; type++)
      if (strcmp(type->name, "ps2") == 0) return(type);

   /* ps2 was not found!!! */
   return(NULL);
}

/*
 * This works with Dexxa Optical Mouse, but because in X same initstring
 * is named ExplorerPS/2 so I named it in the same way.
 */
static Gpm_Type *I_exps2(int fd, unsigned short flags,
          struct Gpm_Type *type, int argc, char **argv)
{
   static unsigned char s1[] = { 243, 200, 243, 200, 243, 80, };

   if (check_no_argv(argc, argv)) return NULL;

   write (fd, s1, sizeof (s1));
   usleep (30000);
   tcflush (fd, TCIFLUSH);
   return type;
}

static Gpm_Type *I_twid(int fd, unsigned short flags,
         struct Gpm_Type *type, int argc, char **argv)
{

   if (check_no_argv(argc, argv)) return NULL;

   if (twiddler_key_init() != 0) return NULL;
   /*
   * the twiddler is a serial mouse: just drop dtr
   * and run at 2400 (unless specified differently) 
   */
   if((which_mouse->opt_baud)==DEF_BAUD) (which_mouse->opt_baud) = 2400;
   argv[1] = "dtr"; /* argv[1] is guaranteed to be NULL (this is dirty) */
   return I_serial(fd, flags, type, argc, argv);
}

static Gpm_Type *I_calus(int fd, unsigned short flags,
          struct Gpm_Type *type, int argc, char **argv)
{
   if (check_no_argv(argc, argv)) return NULL;

   if ((which_mouse->opt_baud) == 1200) (which_mouse->opt_baud)=9600; /* default to 9600 */
   return I_serial(fd, flags, type, argc, argv);
}

/* synaptics touchpad, ps2 version: Henry Davies */
static Gpm_Type *I_synps2(int fd, unsigned short flags,
           struct Gpm_Type *type, int argc, char **argv)
{
   syn_ps2_init (fd);
   return type;
}


static Gpm_Type *I_summa(int fd, unsigned short flags,
          struct Gpm_Type *type, int argc, char **argv) 
{
   void resetsumma()
   {
      write(fd,0,1); /* Reset */
      usleep(400000); /* wait */
   }
   int waitsumma()
   {
      struct timeval timeout;
      fd_set readfds;
      int err;
      FD_ZERO(&readfds);  FD_SET(fd, &readfds);
      timeout.tv_sec = 0; timeout.tv_usec = 200000;
      err = select(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
      return(err);
   }
   int err;
   char buffer[255];
   char config[5];
   /* Summasketchtablet (from xf86summa.o: thanks to Steven Lang <tiger@tyger.org>)*/
   #define SS_PROMPT_MODE  "B"     /* Prompt mode */
   #define SS_FIRMID       "z?"    /* Request firmware ID string */
   #define SS_500LPI       "h"     /* 500 lines per inch */
   #define SS_READCONFIG   "a"
   #define SS_ABSOLUTE     "F"     /* Absolute Mode */
   #define SS_TABID0       "0"     /* Tablet ID 0 */
   #define SS_UPPER_ORIGIN "b"     /* Origin upper left */
   #define SS_BINARY_FMT   "zb"    /* Binary reporting */
   #define SS_STREAM_MODE  "@"     /* Stream mode */
   /* Geniustablet (hisketch.txt)*/
   #define GEN_MMSERIES ":"
   char GEN_MODELL=0x7f;

   /* Set speed to 9600bps */
   setspeed (fd, 1200, 9600, 1, B9600|CS8|CREAD|CLOCAL|HUPCL|PARENB|PARODD);  
   resetsumma(); 
 
   write(fd, SS_PROMPT_MODE, strlen(SS_PROMPT_MODE));
  
   if (strstr(type->name,"acecad")!=NULL) summaid=11;

   if (summaid<0) { /* Summagraphics test */
      /* read the Summa Firm-ID */
      write(fd, SS_FIRMID, strlen(SS_FIRMID));
      err=waitsumma();
      if (!((err == -1) || (!err))) {
         summaid=10; /* Original Summagraphics */
         read(fd, buffer, 255); /* Read Firm-ID */
      }
   }
  
   if (summaid<0) { /* Genius-test */
      resetsumma();
      write(fd,GEN_MMSERIES,1); 
      write(fd,&GEN_MODELL,1); /* Read modell */
      err=waitsumma();
      if (!((err == -1) || (!err))) { /* read Genius-ID */
           err=waitsumma();
         if (!((err == -1) || (!err))) {
            err=waitsumma();
            if (!((err == -1) || (!err))) {
               read(fd,&config,1);
               summaid=(config[0] & 224) >> 5; /* genius tablet-id (0-7)*/
            }
         } 
      }
   } /* end of Geniustablet-test */

   /* unknown tablet ?*/
   if ((summaid<0) || (summaid==11)) { 
      resetsumma(); 
      write(fd, SS_BINARY_FMT SS_PROMPT_MODE, 3);
   }

   /* read tablet size */
   err=waitsumma();  
   if (!((err == -1) || (!err))) read(fd,buffer,sizeof(buffer));
   write(fd,SS_READCONFIG,1);
   read(fd,&config,5);
   summamaxx=(config[2]<<7 | config[1])-(SUMMA_BORDER*2);
   summamaxy=(config[4]<<7 | config[3])-(SUMMA_BORDER*2);
  
   write(fd,SS_ABSOLUTE SS_STREAM_MODE SS_UPPER_ORIGIN,3);
   if (summaid<0) write(fd,SS_500LPI SS_TABID0 SS_BINARY_FMT,4);

   return type;
}

static Gpm_Type *I_mtouch(int fd, unsigned short flags,
           struct Gpm_Type *type, int argc, char **argv)
{
   struct termios tty;

   /* Set speed to 9600bps (copied from I_summa, above :) */
   tcgetattr(fd, &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;
   tty.c_cflag = B9600|CS8|CREAD|CLOCAL|HUPCL;
   tcsetattr(fd, TCSAFLUSH, &tty);


   /* Turn it to "format tablet" and "mode stream" */
   write(fd,"\001MS\r\n\001FT\r\n",10);
  
   return type;
}

/* simple initialization for the gunze touchscreen */
static Gpm_Type *I_gunze(int fd, unsigned short flags,
           struct Gpm_Type *type, int argc, char **argv)
{
   struct termios tty;
   FILE *f;
   char s[80];
   int i, calibok = 0;

   #define GUNZE_CALIBRATION_FILE SYSCONFDIR "/gpm-calibration"
   /* accept a few options */
   static argv_helper optioninfo[] = {
      {"smooth",   ARGV_INT, u: {iptr: &gunze_avg}},
      {"debounce", ARGV_INT, u: {iptr: &gunze_debounce}},
      /* FIXME: add corner tapping */
      {"",       ARGV_END}
   };
   parse_argv(optioninfo, argc, argv);
    
   /* check that the baud rate is valid */
   if ((which_mouse->opt_baud) == DEF_BAUD) (which_mouse->opt_baud) = 19200; /* force 19200 as default */
   if ((which_mouse->opt_baud) != 9600 && (which_mouse->opt_baud) != 19200) {
      gpm_report(GPM_PR_ERR,GPM_MESS_GUNZE_WRONG_BAUD,option.progname, argv[0]);
      (which_mouse->opt_baud) = 19200;
   }
   tcgetattr(fd, &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;
   tty.c_cflag = ((which_mouse->opt_baud) == 9600 ? B9600 : B19200) |CS8|CREAD|CLOCAL|HUPCL;
   tcsetattr(fd, TCSAFLUSH, &tty);

   /* FIXME: try to find some information about the device */

   /* retrieve calibration, if not existent, use defaults (uncalib) */
   f = fopen(GUNZE_CALIBRATION_FILE, "r");
   if (f) {
      fgets(s, 80, f); /* discard the comment */
      if (fscanf(f, "%d %d %d %d", gunze_calib, gunze_calib+1,
         gunze_calib+2, gunze_calib+3) == 4)
         calibok = 1;
   /* Hmm... check */
      for (i=0; i<4; i++)
         if (gunze_calib[i] & ~1023) calibok = 0;
      if (gunze_calib[0] == gunze_calib[2]) calibok = 0;
      if (gunze_calib[1] == gunze_calib[3]) calibok = 0;
      fclose(f);
   }
   if (!calibok) {
      gpm_report(GPM_PR_ERR,GPM_MESS_GUNZE_CALIBRATE, option.progname);
      gunze_calib[0] = gunze_calib[1] = 128; /* 1/8 */
      gunze_calib[2] = gunze_calib[3] = 896; /* 7/8 */
   }
   return type;
}


/* simple initialization for the elo touchscreen */
static Gpm_Type *I_etouch(int fd, unsigned short flags,
			  struct Gpm_Type *type, int argc, char **argv)
{
  struct termios tty;
  FILE *f;
  char s[80];
  int i, calibok = 0;

  /* Calibration config file (copied from I_gunze, below :) */
  #define ELO_CALIBRATION_FILE SYSCONFDIR "/gpm-calibration"
   /* accept a few options */
   static argv_helper optioninfo[] = {
         {"clickontouch",  ARGV_BOOL, u: {iptr: &elo_click_ontouch}, value: !0},
         {"",       ARGV_END} 
   };
   parse_argv(optioninfo, argc, argv);

  /* Set speed to 9600bps (copied from I_summa, above :) */
  tcgetattr(fd, &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;
  tty.c_cflag = B9600|CS8|CREAD|CLOCAL|HUPCL;
  tcsetattr(fd, TCSAFLUSH, &tty);

  /* retrieve calibration, if not existent, use defaults (uncalib) */
  f = fopen(ELO_CALIBRATION_FILE, "r");
  if (f) {
	fgets(s, 80, f); /* discard the comment */
	if (fscanf(f, "%d %d %d %d", gunze_calib, gunze_calib+1,
		   gunze_calib+2, gunze_calib+3) == 4)
	    calibok = 1;
	/* Hmm... check */
	for (i=0; i<4; i++)
	    if ((gunze_calib[i] & 0xfff) != gunze_calib[i]) calibok = 0;
	if (gunze_calib[0] == gunze_calib[2]) calibok = 0;
	if (gunze_calib[1] == gunze_calib[3]) calibok = 0;
	fclose(f);
  }
  if (!calibok) {
        gpm_report(GPM_PR_ERR,GPM_MESS_ELO_CALIBRATE, option.progname, ELO_CALIBRATION_FILE);
        /* "%s: etouch: calibration file %s absent or invalid, using defaults" */
	gunze_calib[0] = gunze_calib[1] = 0x010; /* 1/16 */
	gunze_calib[2] = gunze_calib[3] = 0xff0; /* 15/16 */
  }
  return type;
}



/*  Genius Wizardpad tablet  --  Matt Kimball (mkimball@xmission.com)  */
static Gpm_Type *I_wp(int fd, unsigned short flags,
            struct Gpm_Type *type, int argc, char **argv)
{
   struct termios tty;
   char tablet_info[256];
   int count, pos, size;

   /* Set speed to 9600bps (copied from I_summa, above :) */
   tcgetattr(fd, &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;
   tty.c_cflag = B9600|CS8|CREAD|CLOCAL|HUPCL;
   tcsetattr(fd, TCSAFLUSH, &tty);

   /*  Reset the tablet (':') and put it in remote mode ('S') so that
     it isn't sending anything to us.  */
   write(fd, ":S", 2);
   tcsetattr(fd, TCSAFLUSH, &tty);

   /*  Query the model of the tablet  */
   write(fd, "T", 1);
   sleep(1);
   count = read(fd, tablet_info, 255);

   /*  The tablet information should start with "KW" followed by the rest of
     the model number.  If it isn't there, it probably isn't a WizardPad.  */
   if(count < 2) return NULL;
   if(tablet_info[0] != 'K' || tablet_info[1] != 'W') return NULL;

   /*  Now, we want the width and height of the tablet.  They should be 
     of the form "X###" and "Y###" where ### is the number of units of
     the tablet.  The model I've got is "X130" and "Y095", but I guess
     there might be other ones sometime.  */
   for(pos = 0; pos < count; pos++) {
      if(tablet_info[pos] == 'X' || tablet_info[pos] == 'Y') {
         if(pos + 3 < count) {
            size = (tablet_info[pos + 1] - '0') * 100 +
                   (tablet_info[pos + 2] - '0') * 10 +
                   (tablet_info[pos + 3] - '0');
            if(tablet_info[pos] == 'X') {
               wizardpad_width = size;
            } else {
               wizardpad_height = size;
            }
         }
      }
   }

   /*  Set the tablet to stream mode with 180 updates per sec.  ('O')  */
   write(fd, "O", 1);

   return type;
}

/*========================================================================*/
/* Finally, the table */
#define STD_FLG (CREAD|CLOCAL|HUPCL)

/*
 * Note that mman must be the first, and ms the second (I use this info
 * in mouse-test.c, as a quick and dirty hack
 *
 * We should clean up mouse-test and sort the table alphabeticly! --nico
 */

 /*
   * For those who are trying to add a new type, here a brief
   * description of the structure. Please refer to gpmInt.h and gpm.c
   * for more information:
   *
   * The first three strings are the name, an help line, a long name (if any)
   * Then come the functions: the decoder and the initializazion function
   *     (called I_* and M_*)
   * Follows an array of four bytes: it is the protocol-identification, based
   *     on the first two bytes of a packet: if
   *     "((byte0 & proto[0]) == proto[1]) && ((byte1 & proto[2]) == proto[3])"
   *     then we are at the beginning of a packet.
   * The following numbers are:
   *     bytes-per-packet,
   *     bytes-to-read (use 1, bus mice are pathological)
   *     has-extra-byte (boolean, for mman pathological protocol)
   *     is-absolute-coordinates (boolean)
   * Finally, a pointer to a repeater function, if any.
   */

Gpm_Type mice[]={

   {"mman", "The \"MouseMan\" and similar devices (3/4 bytes per packet).",
           "Mouseman", M_mman, I_serial, CS7 | STD_FLG, /* first */
                                {0x40, 0x40, 0x40, 0x00}, 3, 1, 1, 0, 0},
   {"ms",   "The original ms protocol, with a middle-button extension.",
           "", M_ms, I_serial, CS7 | STD_FLG,
                                {0x40, 0x40, 0x40, 0x00}, 3, 1, 0, 0, 0},
   {"acecad",  "Acecad tablet absolute mode(Sumagrapics MM-Series mode)",
           "", M_summa, I_summa, STD_FLG,
                                {0x80, 0x80, 0x00, 0x00}, 7, 1, 0, 1, 0}, 
   {"bare", "Unadorned ms protocol. Needed with some 2-buttons mice.",
           "Microsoft", M_bare, I_serial, CS7 | STD_FLG,
                                {0x40, 0x40, 0x40, 0x00}, 3, 1, 0, 0, 0},
   {"bm",   "Micro$oft busmice and compatible devices.",
           "BusMouse", M_bm, I_empty, STD_FLG, /* bm is sun */
                                {0xf8, 0x80, 0x00, 0x00}, 3, 3, 0, 0, 0},
   {"brw",  "Fellowes Browser - 4 buttons (and a wheel) (dual protocol?)",
           "", M_brw, I_pnp, CS7 | STD_FLG,
                                {0xc0, 0x40, 0xc0, 0x00}, 4, 1, 0, 0, 0},
   {"cal", "Calcomp UltraSlate",
           "", M_calus, I_calus, CS8 | CSTOPB | STD_FLG,
                                {0x80, 0x80, 0x80, 0x00}, 6, 6, 0, 1, 0},
   {"calr", "Calcomp UltraSlate - relative mode",
           "", M_calus_rel, I_calus, CS8 | CSTOPB | STD_FLG,
                                {0x80, 0x80, 0x80, 0x00}, 6, 6, 0, 0, 0},
  {"etouch",  "EloTouch touch-screens (only button-1 events, by now)",
           "", M_etouch, I_etouch, STD_FLG,
                                {0xFF, 0x55, 0xFF, 0x54}, 7, 1, 0, 1, NULL}, 
#ifdef HAVE_LINUX_INPUT_H
   {"evdev", "Linux Event Device",
            "", M_evdev, I_empty, STD_FLG,
                        {0x00, 0x00, 0x00, 0x00} , 16, 16, 0, 0, NULL},
#endif /* HAVE_LINUX_INPUT_H */
   {"exps2",   "IntelliMouse Explorer (ps2) - 3 buttons, wheel unused",
           "ExplorerPS/2", M_imps2, I_exps2, STD_FLG,
                                {0xc0, 0x00, 0x00, 0x00}, 4, 1, 0, 0, 0},
#ifdef HAVE_LINUX_JOYSTICK_H
   {"js",   "Joystick mouse emulation",
           "Joystick", M_js, NULL, 0,
                              {0xFC, 0x00, 0x00, 0x00}, 12, 12, 0, 0, 0},
#endif
   {"genitizer", "\"Genitizer\" tablet, in relative mode.",
           "", M_geni, I_serial, CS8|PARENB|PARODD,
                                {0x80, 0x80, 0x00, 0x00}, 3, 1, 0, 0, 0},
   {"gunze",  "Gunze touch-screens (only button-1 events, by now)",
           "", M_gunze, I_gunze, STD_FLG,
                                {0xF9, 0x50, 0xF0, 0x30}, 11, 1, 0, 1, NULL}, 
   {"imps2","Microsoft Intellimouse (ps2)-autodetect 2/3 buttons,wheel unused",
           "", M_imps2, I_imps2, STD_FLG,
                                {0xC0, 0x00, 0x00, 0x00}, 4, 1, 0, 0, R_imps2},
   {"logi", "Used in some Logitech devices (only serial).",
           "Logitech", M_logi, I_logi, CS8 | CSTOPB | STD_FLG,
                                {0xe0, 0x80, 0x80, 0x00}, 3, 3, 0, 0, 0},
   {"logim",  "Turn logitech into Mouse-Systems-Compatible.",
           "", M_logimsc, I_serial, CS8 | CSTOPB | STD_FLG,
                                {0xf8, 0x80, 0x00, 0x00}, 5, 1, 0, 0, 0},
   {"mm",   "MM series. Probably an old protocol...",
           "MMSeries", M_mm, I_serial, CS8 | PARENB|PARODD | STD_FLG,
                                {0xe0, 0x80, 0x80, 0x00}, 3, 1, 0, 0, 0},
   {"ms3", "Microsoft Intellimouse (serial) - 3 buttons, wheel unused",
           "", M_ms3, I_pnp, CS7 | STD_FLG,
                                {0xc0, 0x40, 0xc0, 0x00}, 4, 1, 0, 0, R_ms3},
   {"ms+", "Like 'ms', but allows dragging with the middle button.",
           "", M_ms_plus, I_serial, CS7 | STD_FLG,
                                {0x40, 0x40, 0x40, 0x00}, 3, 1, 0, 0, 0},
   {"ms+lr", "'ms+', but you can reset m by pressing lr (see man page).",
           "", M_ms_plus_lr, I_serial, CS7 | STD_FLG,
                                {0x40, 0x40, 0x40, 0x00}, 3, 1, 0, 0, 0},
   {"msc",  "Mouse-Systems-Compatible (5bytes). Most 3-button mice.",
           "MouseSystems", M_msc, I_serial, CS8 | CSTOPB | STD_FLG,
                                {0xf8, 0x80, 0x00, 0x00}, 5, 1, 0, 0, R_msc},
   {"mtouch",  "MicroTouch touch-screens (only button-1 events, by now)",
           "", M_mtouch, I_mtouch, STD_FLG,
                                {0x80, 0x80, 0x80, 0x00}, 5, 1, 0, 1, NULL}, 
   {"ncr",  "Ncr3125pen, found on some laptops",
           "", M_ncr, NULL, STD_FLG,
                                {0x08, 0x08, 0x00, 0x00}, 7, 7, 0, 1, 0},
   {"netmouse","Genius NetMouse (ps2) - 2 buttons and 2 buttons 'up'/'down'.", 
           "", M_netmouse, I_netmouse, CS7 | STD_FLG,
                                {0xc0, 0x00, 0x00, 0x00}, 4, 1, 0, 0, 0},
   {"pnp",  "Plug and pray. New mice may not run with '-t ms'.",
           "", M_bare, I_pnp, CS7 | STD_FLG,
                                {0x40, 0x40, 0x40, 0x00}, 3, 1, 0, 0, 0},
   {"ps2",  "Busmice of the ps/2 series. Most busmice, actually.",
           "PS/2", M_ps2, I_ps2, STD_FLG,
                                {0xc0, 0x00, 0x00, 0x00}, 3, 1, 0, 0, R_ps2},
   {"sun",  "'msc' protocol, but only 3 bytes per packet.",
           "", M_sun, I_serial, CS8 | CSTOPB | STD_FLG,
                                {0xf8, 0x80, 0x00, 0x00}, 3, 1, 0, 0, R_sun},
   {"summa",  "Summagraphics or Genius tablet absolute mode(MM-Series)",
           "", M_summa, I_summa, STD_FLG,
                                {0x80, 0x80, 0x00, 0x00}, 5, 1, 0, 1, R_summa},
   {"syn", "The \"Synaptics\" serial TouchPad.",
           "synaptics", M_synaptics_serial, I_serial, CS7 | STD_FLG,
                                {0x40, 0x40, 0x40, 0x00}, 6, 6, 1, 0, 0},
   {"synps2", "The \"Synaptics\" PS/2 TouchPad",
           "synaptics_ps2", M_synaptics_ps2, I_synps2, STD_FLG,
                                {0x80, 0x80, 0x00, 0x00}, 6, 1, 1, 0, 0},
   {"twid", "Twidddler keyboard",
           "", M_twid, I_twid, CS8 | STD_FLG,
                                {0x80, 0x00, 0x80, 0x80}, 5, 1, 0, 0, 0},
   {"vsxxxaa", "The DEC VSXXX-AA/GA serial mouse on DEC workstations.",
           "", M_vsxxx_aa, I_serial, CS8 | PARENB | PARODD | STD_FLG,
                                {0xe0, 0x80, 0x80, 0x00}, 3, 1, 0, 0, 0},
   {"wacom","Wacom Protocol IV Tablets: Pen+Mouse, relative+absolute mode",
           "", M_wacom, I_wacom, STD_FLG,
                                {0x80, 0x80, 0x80, 0x00}, 7, 1, 0, 0, 0},
   {"wp",   "Genius WizardPad tablet",
           "wizardpad", M_wp, I_wp, STD_FLG,
                                {0xFA, 0x42, 0x00, 0x00}, 10, 1, 0, 1, 0},
   {"",     "",
           "", NULL, NULL, 0,
                                {0x00, 0x00, 0x00, 0x00}, 0, 0, 0, 0, 0}
};

/*------------------------------------------------------------------------*/
/* and the help */

int M_listTypes(void)
{
   Gpm_Type *type;

   printf(GPM_MESS_VERSION "\n");
   printf(GPM_MESS_AVAIL_MYT);
   for (type=mice; type->fun; type++)
      printf(GPM_MESS_SYNONYM, type->repeat_fun?'*':' ',
      type->name, type->desc, type->synonyms);

   putchar('\n');

   return 1; /* to exit() */
}

/* indent: use three spaces. no tab. not two or four. three */

/* Local Variables: */
/* c-indent-level: 3 */
/* End: */
