/*
 * synaptics.c - support for the synaptics serial and ps2 touchpads
 *
 * Copyright 1999   hdavies@ameritech.net (Henry Davies)
 *                  Geert Van der Plas provided the code to support
 *                        older Synaptics PS/2 touchpads.
 *
 *   Synaptics Passthrough Support Copyright (C) 2002 Linuxcare Inc.
 *   dkennedy@linuxcare.com (David Kennedy)
 * 
 *   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.
 *
 ********/

/*
** Design/Goals
**     I want to use the Synaptics Serial touchpad as a cursor device under
**     Linux (gpm).  With this device I want to support operations similar
**     to those supported by the Synaptics WinDOS driver, and some others 
**     of my own devising.
**
** Features:
**     Corner Clicks
**         This recognizes taps on the touchpad in the corner(s) and
**         translates them into specific actions.  Initially I am looking
**         at actions on the order of alternate button clicks.  Other 
**         alternatives include drags and whatnot.
**     Edge Extensions
**         This recognizes that the finger has moved from the center region
**         of the touchpad and dragged to the edge area.  At which point
**         I want to be able to extend the motion by automatically moving 
**         in the direction of the edge.
**     Toss n Catch
**         This recognizes a quick motion of the finger on the touchpad and
**         uses that to define a velocity vector for the cursor.  A tap
**         on the touchpad at a later time catches (stops) the cursor.
**     Tap n Drag
**         A quick tap of the touchpad followed by finger motion on the 
**         touchpad initiates what would be a drag with a normal mouse
**         type device.
**     Pressure Sensitive Velocity
**         The Synaptics touchpad indicates the touch pressure of the finger
**         (really an interface area) this is used to accelerate the cursor
**         motion.  This can be used in the normal motion, Tap n Drag, or
**         Edge Extension modes.  In normal motion and Tap n Drag this may 
**         be awkward due to increased friction caused by the pressure.
**
** Parameters:
**     search below for configuration constants
*/


/* Pebl - pebl@diku.dk 20/12/2001 08:06.
**
** I want to have more features :). Every touch pad (ps2 and serial) is now be
** supported according to STIG and every feature/capability is read.  However
** there seems to be some very old serial touchpads that have an advanced
** mode not mentioned in STIG.
**
**
** Further I have added the following
**
**  Emulation of scrolling
**        The window drivers allow using the edge to emulate a wheel
**        mouse. When putting the finger at the right edge, a movement up or
**        down translates to wheel movement. Taking the finger to the
**        top/button edge keeps the wheel turning.  Lift the finger again to
**        operate normally again. Another option is to do a toss scrolling.
**        Added horizontal scrolling with same parameters as vertical scrolling.
**
**  Stick attached to a synaptic touchpad (aka styk)
**        Some touchpads have a stick "attached", so they share the same port.
**        In absolute mode the stick protocol is a simple ps2 protocol, except
**        it is sent in an absolute packet. (In relative mode both uses ps2)
**        Some sticks send packets when pressed (different from moved), which
**        will be reported as left button.
**
**  4 Way button attached to a touchpad
**        Likewise some touchpads have a round button giving the choices of
**        four way to press the button. The packet is nearly identical to the
**        stick, turn off the stick if you have this button. This is no longer
**        needed.  The button can act as a mouse or as buttons which can be
**        changed on the fly. The buttons can be configured just as the
**        touchpads corners.
**
**  Four buttons to work
**        Some touchpads have 4 buttons. Only 3 was read and the last was set to 
**        up or down "button". They can be configured just as the
**        touchpads corners.
**
**  Multiple fingers
**        I have added an option to detect such by looking at pressure levels,
**        which is not that great. If the pad have the capability to detect it,
**        this is used instead, but this far from optimal. It does detect two
**        horizontal parted finger if in same vertical position, otherwise the
**        detection is bad. This is/was a problem as my wrist lays on the
**        laptop which gives a 45 deg to horizontal for my fingers. Now I try
**        to add further detections, which works for me in 95% of the
**        time. 
**
**  Multi finger tap
**        Using 1,2 or 3 fingers to make a tap translates into left, right or
**        middle button pressed. I originally thought about doing what is
**        called a "HOP" where after a quick finger shift the distance of the
**        hop decides which button is pressed. I dropped it partly because
**        synaptics did in version 3.2 and because it was harder than I
**        thought. Multifinger taps can be configured just as the touchpads corners.
**
**  Multi finger less sensitive
**        Adding or removing a finger or just accidently touch the pad with
**        the palm while using the pad causes the pad to report the average
**        position between the touched places.  This of cause gives annoying
**        erratic movements. When adding or removing fingers are detected
**        the mouse is stop for some small time to avoid this. This is not
**        perfect as the finger detection is not perfect, but it helps a lot!
**
**  Palm detection
**        Some touchpads tries to detect whether the reported data is from a
**        palm or similar.  When reading this signal the movement is
**        stopped. Except the detection does not work very well :(
**
**
**  Enabling/disabling touchpad
**        A new corner action is to disable the touchpad. It is enabled again
**        by tapping the same corner. This is useful if I know I am going
**        to write a lot. The stick still works.
**
**  Debugging corner action  
**        A corner action now allows toggling the debug information. 
**
**
** The structure has changed to handle both serial and ps2 touchpads. Many
** variable names have changed.                  
**
** The documentation referred to is from
**  "Synaptics TouchPad Interfacing Guide" revision 2.5 
** or just STIG
**                                                                                 
**/


/*
** TODO
**   - handle other versions of synaptics touchpads (mine is 3.4 firmware)
**     (this should be close)
**   - test this with more Synaptics touch pads (laptops and such)
**   - provide a configuration interface to adjust parameters and
**     enabled features
**   - determine appropriate ranges for adjusting parameters
**
** TODO (pebl)
**   - Two pads can not be used at the same time (internal/external)
**   - Move a lot of the comments to a readme file.
**   - Better detection of spontaneous reseting touchpad.
**   - Implement resend command for serial connection.
**   - Only start scrolling if mostly vertical movement.
**   - Disable/enable command for serial touchpad.
**   - source splitting of normal touchpad functions and synaptic specific and
**     add other touchpads.
**   - Solve the problem with 4-way-buttons.
*/



/**
** Notation:
** A gesture means a motion or action that is not a regular mouse movement.
** 
** Wmode is an absolute mode where no gesture is signaled by the touchpad, only
** supported on newer versions.
**
** variables *_enabled are use for enable behaviors that may not supported in
** the touchpad.
**
** Many variables have two counterparts (and some that don't should have): a
v** *_time and *_packet, where the first is in msec and the latter is the same
** time converted to the number of packets, when using 80 packets per second.
**
** Variables:
** last_* is for last reviewed packet, not last reported/calculated movement/event.
** was_*  is for last event, not necessary last packet.
**
**
** Data process line:
**
** syn_process_ps2_data   ---- syn_translate_serial_data    --                         -----------------------------------------------
**                                (handles also wmode)       |                         |                            |
**                                                           |                         |                            V
**                                                           |- syn_preprocess_report --- syn_process_wmode_report --- syn_process_report
**                                                           |                          
** syn_process_serial_data --- syn_translate_ps2_wmode_data --                                                       
**                          |                                |                          
**                          |                                |                          
**                          -- syn_translate_ps2_data     ----                          
**
**
*/


#include <math.h>                /* ceil */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#ifdef HAVE_LINUX_INPUT_H
#  include <linux/input.h>
#endif /* HAVE_LINUX_INPUT_H */

#include "headers/gpm.h"
#include "headers/gpmInt.h"       /* which_mouse */
#include "headers/synaptics.h"
#include "headers/message.h"

#define DEBUG                     1
#undef DEBUG

#ifdef DEBUG
#  define DEBUG_SYNAPTIC          1
#  define DEBUG_PARAMS            1
#  define DEBUG_FLUSH             1
#  define DEBUG_RESET             1
#  define DEBUG_SENT_DATA         1
#  define DEBUG_RECEIVED_DATA     1
#  define DEBUG_TOSS              1
#  define DEBUG_PALM              1
#  define DEBUG_STICK             1
#  define DEBUG_FOUR_WAY          1
#  define DEBUG_TAPS              1
#  define DEBUG_MULTI_FINGER      1
#  define DEBUG_CMD               1
#  undef  DEBUG_GETBYTE           1    /* this is VERY verbose */
#  undef  DEBUG_PUTBYTE           1    /* this is VERY verbose */
#  undef  DEBUG_PUTBYTE_ACK       1    /* this is VERY verbose */
#  define DEBUG_REPORTS           1    /* this is VERY verbose */
#  define DEBUG_REPORTS_NO_GARBAGE 1   /* Don't show pressure below 11 */
#  undef  DEBUG_RAW               1
#endif


/* The next is UGLY, but I only want debug info from this file */
/* it's really ugly and you shouldn't use this. Instead use
 * gpm -D | grep filename.c.
 * ifdef DEBUG should not be used anymore! */
/* pebl: But this require that I boot my machine with gpm -D which I don't. The
   intention is that when I am doing my normal doing, and the touchpad suddenly
   behaves strange, I can start debuging without restarting gpm, using a
   corneraction.
*/

/* BAD CODE BEGIN *
#ifdef DEBUG_SYNAPTIC
#  include <stdarg.h>
   static int debug_syn_to_stderr = 1;

   static void gpm_report_static(int line, char *file, int stat, char* fmt, ... ) {

     va_list ap;
     va_start(ap, fmt);
     if (debug_syn_to_stderr){
       vfprintf(stderr, fmt, ap);
       fputs("\n", stderr);
     }else{
       gpm_report(line,file,stat,fmt,&ap);
     }
     va_end(ap);

   }
#  define gpm_report gpm_report_static
#endif

* END BADE CODE */

/* Prototype */
typedef unsigned char byte;

static void syn_ps2_absolute_mode(int fd);
static char *syn_model_name (int sensor);
static byte syn_ps2_send_cmd(int fd, int stick, byte cmd); 


/* Defines */

#define abs(value)              ((value) < 0 ? -(value) : (value))
#define check_bits(value,mask)  (((value) & (mask)) == (mask))
#define sqr(value)              ((value) * (value))
#define distance(dx,dy)         (sqr(dx) + sqr(dy))
#define mod4(value)             ((value) % 4)
#define max(x,y)                ((x)>(y)?(x):(y))

/*
** Define the edge bit values.
*/
#define LEFT_EDGE           0x01
#define RIGHT_EDGE          0x02
#define TOP_EDGE            0x04
#define BOTTOM_EDGE         0x08
#define UPPER_LEFT_CORNER   (LEFT_EDGE  | TOP_EDGE)
#define LOWER_LEFT_CORNER   (LEFT_EDGE  | BOTTOM_EDGE)
#define UPPER_RIGHT_CORNER  (RIGHT_EDGE | TOP_EDGE)
#define LOWER_RIGHT_CORNER  (RIGHT_EDGE | BOTTOM_EDGE)
#define ALL_EDGES           (LEFT_EDGE  | RIGHT_EDGE | TOP_EDGE | BOTTOM_EDGE)
#define is_corner(edges)    (((edges) & (LEFT_EDGE | RIGHT_EDGE)) && \
			     ((edges) & (TOP_EDGE  | BOTTOM_EDGE)))

/*
** Define the behavior while doing a multifinger tap
*/

#define MULTI_FINGER_TAP_REAL   0
#define MULTI_FINGER_TAP_NO_DEC 1
#define MULTI_FINGER_TAP_STATIC 2

/*
** Define the action button bit values.
*/
#define RIGHT_BUTTON        0x01
#define MIDDLE_BUTTON       0x02
#define LEFT_BUTTON         0x04
#define FOURTH_BUTTON       0x08
#define UP_BUTTON           0x10
#define DOWN_BUTTON         0x20
#define B_SCROLL_BUTTON     0x40
#define F_SCROLL_BUTTON     0x80
#define ONE_FINGER          0x01
#define TWO_FINGERS         0x02
#define THREE_FINGERS       0x04
#define MULTI_UP_BUTTON     0x01
#define MULTI_DOWN_BUTTON   0x02
#define MULTI_LEFT_BUTTON   0x04
#define MULTI_RIGHT_BUTTON  0x08
#define MULTI_5_BUTTON      0x10
#define MULTI_6_BUTTON      0x20
#define MULTI_7_BUTTON      0x40
#define MULTI_8_BUTTON      0x80
#define STICK_RIGHT_BUTTON  0x01
#define STICK_MIDDLE_BUTTON 0x02
#define STICK_LEFT_BUTTON   0x04
#define STICK_FOURTH_BUTTON 0x08
#define STICK_FIFTH_BUTTON  0x10

/*
** Define additional gpm button bit values. (gpm.h)
*/
#define GPM_B_FORWARD       128
#define GPM_B_BACKWARD      64
#define GPM_B_NOT_SET       (-1)

/*
** Define the guest and touchpad devices.
*/
#define DEVICE_TOUCHPAD     0
#define DEVICE_STICK        1


/****************************************************************************
**
** Configuration constants.
**
** These are configurable through the configuration file.
**
****************************************************************************/

/* Important usages constants */
static int   wmode_enabled	         = 1;   /* Is wmode enabled. Do not disable unless you know why */
static int   touchpad_enabled            = 1;   /* Disable the touch pad, see corner action turn_on_off */
static int   touchpad_buttons_enabled    = 1;   /* Enable the use of the pads buttons */
static int   reset_on_error_enabled      = 0;   /* If a packet does not conform to any absolute protocol 
					         * should we reset the touchpad? This is wrong, because we
					         * should rather find out why it does that in first place.
					         * Do not turn it on per default. */

/* Normal motion dependend constants */
static int   touchpad_motion_enabled     = 1;   /* Enable the use of the pad to move the mouse */
static int   finger_threshold	         = 30;  /* Pressure before it is a finger on the pad */
static int   finger_off_threshold        = 25;  /* Pressure before the finger off the touchpad again */
static int   motion_method               = 3;   /* Which motion calculation method should be used */
static int   fraction_motion_enabled     = 1;   /* When calculating the motions use fractions */
static float standard_speed_factor       = 0.12;/* The relation between finger and mouse movement */

/* Pressure induced speed related configuration constants */
static int   edge_motion_enabled         = 1;   /* Is edge motion enabled */
static int   tap_hold_edge_motion_enabled= 1;   /* Enable edge motion while holding a tap */
static int   pressure_speed_enabled      = 1;   /* Does pressure control speed in non edges */
static int   edge_pressure_speed_enabled = 1;   /* Does pressure control speed of edge motion */
static int   default_edge_speed          = 20;  /* Default speed at edges */
static int   speedup_pressure            = 80;  /* At what pressure should the speedup happen */
static float speedup_pressure_factor     = 0.05;/* Relation between pressure and mouse movement */

/* Gesture related configuration constants for when wmode is enabled. */
static int   tap_gesture_enabled         = 1;   /* Are gestures treaded as taps enabled */
static int   corner_taps_enabled         = 1;   /* Are corner taps enabled (depends on tap gestures)*/
static int   drag_lock_enabled	         = 1;   /* is drag locking enabled */
static int   tap_lower_limit	         = 5;   /* a tap last at least this long */
static int   tap_upper_limit	         = 200; /* a tap is at most this long */
static int   tap_range		         = 100; /* mm finger movement limit */
static int   tap_interval	         = 200; /* a tap reports button press this long */
static int   multiple_tap_delay          = 30;  /* time between reported button pressed */
static int   pads_tap_interval           = 8;   /* if pad sends gestures, what is it's tap_interval */

/* toss/catch related constants */
static int   tossing_enabled             = 1;   /* is toss/catch enabled */
static int   does_toss_use_static_speed  = 1;   /* is toss/catch speed based on toss dist */
static int   min_toss_time               = 100; /* minimum time to start a toss */
static int   max_toss_time               = 300; /* maximum time to start a todd */
static int   prevent_toss_time           = 300; /* ms: 0.25 sec */
static int   min_toss_dist               = 2;   /* Finger must move atleast this far to toss */ 
static int   static_toss_speed           = 70;  /* How fast if static speed */
static float toss_speed_factor           = 0.07;/* How fast if not static speed */

/* edge motion related configuration constants */
static int   x_min_center                = 1632;/* define left edge */
static int   x_max_center                = 5312;/* define right edge */
static int   y_min_center                = 1408;/* define bottom edge */
static int   y_max_center                = 4108;/* define top edge */
static int   y_is_reversed               = 0;   /* Reverse the definition of up and down for the y coordinate */

/* wmode capabilities */
static int   palm_detect_enabled         = 1;   /* Ignore pad packets when palm on pad */
static int   palm_detect_level           = 12;  /* Detecting a palm level (between 0-11) */
static int   multi_finger_tap_enabled    = 1;   /* No. of fingers decides which button is pressed */
static int   multi_finger_tap_behavior   = 2;   /* Behavior when changing no. fingers while in a tap-hold */
static int   multi_finger_stop_enabled   = 1;   /* less sensitive mouse with multi finger */
static int   multi_finger_stop_delay     = 8;   /* how long to stop after a multifinger detection */
static int   fake_finger_layer_enabled   = 1;   /* add an extra software layer to detect multi fingers */

/* Scrolling settings */
static int   scrolling_enabled           = 1;   /* Simulate wheel mouse in at the right and/or left edge */
static int   scrolling_edge              = RIGHT_EDGE; /* Which edge is a the scrolling edge */
static int   scrolling_speed             = 10;  /* less is faster, 1 fastest  */
static float scrolling_button_factor     = 0.5; /* How fast should a button/corner tap scroll, higher faster */
static int   auto_scrolling_enabled      = 1;   /* Moving to the upper/lower edge keeps scrolling up/down*/
static float auto_scrolling_factor       = 2.0; /* How fast should autoscrolling be */
static int   hscrolling_enabled          = 0;   /* Simulate horisontal scrolling */
static int   hscrolling_edge             = BOTTOM_EDGE; /* Simulate horisontal scrolling at this edge */
static int   scrolling_inhibits_taps     = 1;   /* Is taps allowed at when scrolling */

/* Multi Buttons support */
static int   multi_buttons_enabled       = 1;   /* Round button giving 4 choices on some touchpads */
static int   multi_buttons_is_mouse      = 0;   /* Is the button: 4 buttons or does it moves the mouse */
static int   multi_buttons_mouse_speed   = 1;   /* How fast should the 4 buttons move the mouse. 1 slowest */

/* Stick or Guest configurations */
static int   stick_enabled               = 1;   /* Some machines have a touchpad and a stick on
                                                 * same device port. The stick will be ignored in
                                                 * absolute mode, this option try to recognize
                                                 * it's packets. */
static int   stick_motion_enabled        = 1;   /* Can the stick move the mouse */
static float stick_motion_factor         = 1.0; /* How fast should the stick move */
static int   stick_pressure_enabled      = 1;   /* A (hard) press on the stick is reported as left click */
static int   stick_scrolling_enabled     = 0;   /* Does the stick generates scroll instead of motion */
static int   stick_sample_rate           = 200; /* Which sample rate should the stick use */
static int   stick_resolution            = 3;   /* Which resolution should the stick use */


/*
** Types for describing actions.  When adding a new action, place it as
** the last item. It will break old configurefiles otherwise.
*/
typedef enum {
  No_Action          = 0,
  Left_Button_Action,
  Middle_Button_Action,
  Right_Button_Action,
  Fourth_Button_Action,
  Up_Scrolling_Action,
  Down_Scrolling_Action,
  Turn_On_Off_Action,
  Debug_On_Off_Action,
  Reset_Touchpad_Action,
  Toggle_Multi_Button_Action,
  Toggle_Stick_Pressure_Action,
  Toggle_Scrolling_Action,
  Toggle_Buttons_Action,
  Left_Double_Click_Action,
  Right_Double_Click_Action,
  Middle_Double_Click_Action,
  Left_Scrolling_Action,
  Right_Scrolling_Action,
  Mouse_Left_Action,
  Mouse_Right_Action,
  Mouse_Up_Action,
  Mouse_Down_Action, 
  Toggle_Stick_Scrolling_Action,
} action_type;


typedef struct {
  int         action_mask;
  action_type action;
} touchpad_action_type;

/* Do not change the internal order of cases in the following actions as they
 * are hardcoded into configuration reading code. */

static touchpad_action_type corner_actions [] = {
  { UPPER_LEFT_CORNER,  Middle_Button_Action },
  { LOWER_LEFT_CORNER,  Right_Button_Action },
  { UPPER_RIGHT_CORNER, No_Action },
  { LOWER_RIGHT_CORNER, No_Action },
  { 0,                  No_Action } /* stop flag value. */
};

static touchpad_action_type normal_button_actions [] = {
  { LEFT_BUTTON,        Left_Button_Action },
  { MIDDLE_BUTTON,      Middle_Button_Action },
  { RIGHT_BUTTON,       Right_Button_Action },
  { FOURTH_BUTTON,      Fourth_Button_Action },
  { UP_BUTTON,          Up_Scrolling_Action },
  { DOWN_BUTTON,        Down_Scrolling_Action },
  { 0,                  No_Action } /* stop flag value */
};

static touchpad_action_type multi_finger_actions [] = {
  { ONE_FINGER,         Left_Button_Action },
  { TWO_FINGERS,        Right_Button_Action },
  { THREE_FINGERS,      Middle_Button_Action },
  { 0,                  No_Action } /* stop flag value */
};

static touchpad_action_type multi_buttons_actions [] = {
  { MULTI_LEFT_BUTTON,   Middle_Button_Action },
  { MULTI_RIGHT_BUTTON,  Fourth_Button_Action },
  { MULTI_UP_BUTTON,     Up_Scrolling_Action },
  { MULTI_DOWN_BUTTON,   Down_Scrolling_Action },
  { MULTI_5_BUTTON,      Toggle_Multi_Button_Action },
  { MULTI_6_BUTTON,      Left_Double_Click_Action },
  { MULTI_7_BUTTON,      Left_Scrolling_Action },
  { MULTI_8_BUTTON,      Right_Scrolling_Action },
  { 0,                   No_Action } /* stop flag value */
};

static touchpad_action_type multi_buttons_mouse [] = {
  { MULTI_LEFT_BUTTON,   Mouse_Left_Action },
  { MULTI_RIGHT_BUTTON,  Mouse_Right_Action },
  { MULTI_UP_BUTTON,     Mouse_Up_Action },
  { MULTI_DOWN_BUTTON,   Mouse_Down_Action },
  { MULTI_5_BUTTON,      Toggle_Multi_Button_Action },
  { MULTI_6_BUTTON,      Left_Double_Click_Action },
  { MULTI_7_BUTTON,      Left_Scrolling_Action },
  { MULTI_8_BUTTON,      Right_Scrolling_Action },
  { 0,                   No_Action } /* stop flag value */
};

static touchpad_action_type stick_actions [] = {
  { STICK_LEFT_BUTTON,  Left_Button_Action },
  { STICK_MIDDLE_BUTTON,Middle_Button_Action },
  { STICK_RIGHT_BUTTON, Right_Button_Action },
  { STICK_FOURTH_BUTTON,Fourth_Button_Action },
  { STICK_FIFTH_BUTTON, Toggle_Scrolling_Action },
  { 0,                  No_Action } /* stop flag value */
};


/*
** These types are used to read the configuration data from the config file.
*/
typedef enum {
  Integer_Param,
  Float_Param,
  Flag_Param,
  Action_Param
} param_type_type;

typedef struct {
  char            *name;
  param_type_type  p_type;
  union {
    void  *gen_p;   /* avoids complaints by the compiler later on */
    int   *int_p;
    float *float_p;
    int   *flag_p;
    touchpad_action_type *corner_p;
  } addr;
} param_data_type;

static param_data_type param_data [] = {
  /* Important usages constants */
  { "wmode_enabled",		  Flag_Param,	 {&wmode_enabled	      }},
  { "touchpad_enabled", 	  Flag_Param,    {&touchpad_enabled	      }},
  { "touchpad_buttons_enabled",	  Flag_Param,    {&touchpad_buttons_enabled   }},

  /* Normal motion dependend constants */
  { "touchpad_motion_enabled",    Flag_Param,    {&touchpad_motion_enabled    }},
  { "finger_threshold",		  Integer_Param, {&finger_threshold	      }},
  { "finger_off_threshold",	  Integer_Param, {&finger_off_threshold	      }},
  { "motion_method",        	  Integer_Param, {&motion_method	      }},
  { "fraction_motion_enabled",	  Flag_Param,    {&fraction_motion_enabled    }},
  { "standard_speed_factor",      Float_Param,   {&standard_speed_factor      }},

  /* pressure induced speed related configuration parameters */		     
  { "edge_motion_enabled",        Flag_Param,    {&edge_motion_enabled        }},
  { "tap_hold_edge_motion_enabled",Flag_Param,   {&tap_hold_edge_motion_enabled}},
  { "pressure_speed_enabled",     Flag_Param,    {&pressure_speed_enabled     }},
  { "edge_pressure_speed_enabled",Flag_Param,    {&edge_pressure_speed_enabled}},
  { "default_edge_speed",         Integer_Param, {&default_edge_speed         }},
  { "speedup_pressure",           Integer_Param, {&speedup_pressure           }},
  { "speedup_pressure_factor",    Float_Param,   {&speedup_pressure_factor    }},

  /* Gesture related configuration constants for when wmode is enabled. */
  { "tap_gesture_enabled",        Flag_Param,    {&tap_gesture_enabled        }},
  { "corner_taps_enabled",        Flag_Param,    {&corner_taps_enabled        }},  
  { "drag_lock_enabled",	  Flag_Param,	 {&drag_lock_enabled	      }},
  { "tap_lower_limit",		  Integer_Param, {&tap_lower_limit	      }},
  { "tap_upper_limit",		  Integer_Param, {&tap_upper_limit	      }},
  { "tap_range",		  Integer_Param, {&tap_range		      }},
  { "tap_interval",		  Integer_Param, {&tap_interval		      }},
  { "multiple_tap_delay",	  Integer_Param, {&multiple_tap_delay	      }},
  { "pads_tap_interval",	  Integer_Param, {&pads_tap_interval	      }},

  /* toss/catch related parameters */		 			     
  { "tossing_enabled",            Flag_Param,    {&tossing_enabled            }},
  { "does_toss_use_static_speed", Flag_Param,    {&does_toss_use_static_speed }},
  { "min_toss_time",              Integer_Param, {&min_toss_time              }},
  { "max_toss_time",              Integer_Param, {&max_toss_time              }},
  { "prevent_toss_time",          Integer_Param, {&prevent_toss_time          }},
  { "min_toss_dist",              Integer_Param, {&min_toss_dist              }},
  { "static_toss_speed",          Integer_Param, {&static_toss_speed          }},
  { "toss_speed_factor",          Float_Param,   {&toss_speed_factor          }},

  /* edge motion related configuration parameters */			     
  { "x_min_center",               Integer_Param, {&x_min_center               }},
  { "x_max_center",               Integer_Param, {&x_max_center               }},
  { "y_min_center",               Integer_Param, {&y_min_center               }},
  { "y_max_center",               Integer_Param, {&y_max_center               }},
  { "y_is_reversed",              Flag_Param,    {&y_is_reversed              }},

  /* Additional wmode parameters */ 
  { "palm_detect_enabled",	  Flag_Param,	 {&palm_detect_enabled	      }},
  { "palm_detect_level",	  Integer_Param, {&palm_detect_level	      }},
  { "multi_finger_tap_enabled",   Flag_Param,    {&multi_finger_tap_enabled   }},
  { "multi_finger_tap_behavior",  Integer_Param, {&multi_finger_tap_behavior  }},
  { "multi_finger_stop_enabled",  Flag_Param,    {&multi_finger_stop_enabled  }},
  { "multi_finger_stop_delay",    Integer_Param, {&multi_finger_stop_delay    }},
  { "fake_finger_layer_enabled",  Flag_Param,    {&fake_finger_layer_enabled  }},

  /* Scrolling options*/
  { "scrolling_enabled",	  Flag_Param,    {&scrolling_enabled	      }},
  { "auto_scrolling_enabled",	  Flag_Param,    {&auto_scrolling_enabled     }},
  { "scrolling_edge",	          Integer_Param, {&scrolling_edge	      }},
  { "scrolling_speed",	          Integer_Param, {&scrolling_speed	      }},
  { "scrolling_button_factor",    Float_Param,   {&scrolling_button_factor    }},
  { "auto_scrolling_factor",      Float_Param,   {&auto_scrolling_factor      }},
  { "hscrolling_enabled",         Flag_Param,    {&hscrolling_enabled         }},
  { "hscrolling_edge",            Integer_Param, {&hscrolling_edge            }},
  { "scrolling_inhibits_taps",    Flag_Param,    {&scrolling_inhibits_taps    }}, 

  /* Multi buttons support configurations */
  { "multi_buttons_enabled",      Flag_Param,    {&multi_buttons_enabled      }},
  { "multi_buttons_is_mouse",     Flag_Param,    {&multi_buttons_is_mouse     }},
  { "mulit_buttons_mouse_speed",  Integer_Param, {&multi_buttons_mouse_speed  }},
  { "four_way_button_enabled",    Flag_Param,    {&multi_buttons_enabled      }},
  { "four_way_button_is_mouse",   Flag_Param,    {&multi_buttons_is_mouse     }},
  { "four_way_mouse_speed",       Integer_Param, {&multi_buttons_mouse_speed  }},

  /* Stick or Guest configurations */
  { "stick_enabled",	          Flag_Param,    {&stick_enabled	      }},
  { "stick_motion_enabled",       Flag_Param,    {&stick_motion_enabled       }},
  { "stick_motion_factor",        Float_Param,   {&stick_motion_factor        }},
  { "stick_scrolling_enabled",    Flag_Param,    {&stick_scrolling_enabled    }},
  { "stick_pressure_enabled",     Flag_Param,    {&stick_pressure_enabled     }},
  { "stick_sample_rate",          Integer_Param, {&stick_sample_rate          }},
  { "stick_resolution",           Integer_Param, {&stick_resolution           }},

  /* corner tap actions */			 			     
  { "upper_left_action",          Action_Param,  {&corner_actions [0]         }},
  { "lower_left_action",          Action_Param,  {&corner_actions [1]         }},
  { "upper_right_action",         Action_Param,  {&corner_actions [2]         }},
  { "lower_right_action",         Action_Param,  {&corner_actions [3]         }},
  /* no. of fingers tap actions */
  { "one_finger_tap_action",      Action_Param,  {&multi_finger_actions [0]   }},
  { "two_fingers_tap_action",     Action_Param,  {&multi_finger_actions [1]   }},
  { "three_fingers_tap_action",   Action_Param,  {&multi_finger_actions [2]   }},
  /* normal button actions */
  { "left_button_action",         Action_Param,  {&normal_button_actions [0]  }},
  { "middle_button_action",       Action_Param,  {&normal_button_actions [1]  }},
  { "right_button_action",        Action_Param,  {&normal_button_actions [2]  }},
  { "fourth_button_action",       Action_Param,  {&normal_button_actions [3]  }},
  { "up_button_action",           Action_Param,  {&normal_button_actions [4]  }},
  { "down_button_action",         Action_Param,  {&normal_button_actions [5]  }},
  /* 4 way button actions */
  { "four_way_left_button_action", Action_Param, {&multi_buttons_actions [0]  }},
  { "four_way_right_button_action",Action_Param, {&multi_buttons_actions [1]  }},
  { "four_way_up_button_action",   Action_Param, {&multi_buttons_actions [2]  }},
  { "four_way_down_button_action", Action_Param, {&multi_buttons_actions [3]  }},
  /* Synaptic Stick (passthrough,stick) actions */
  { "stick_left_button_action",    Action_Param, {&stick_actions [0]          }},
  { "stick_middle_button_action",  Action_Param, {&stick_actions [1]          }},
  { "stick_right_button_action",   Action_Param, {&stick_actions [2]          }},
  { "stick_fourth_button_action",  Action_Param, {&stick_actions [3]          }},
  { "stick_fifth_button_action",   Action_Param, {&stick_actions [4]          }},
  /* end of list */				 			     
  { NULL,                          Flag_Param,   {NULL                        }}
};


/*
** The information returned in the identification packet.
** STIG page 10
*/
typedef struct {
  int model_code;
  int major;
  int minor;
} identify_type;

/*
** The information returned in the model ID packet.
** STIG page 11
*/

#define MODEL_ROT180_BITS      0x800000  /* bit 23    */
#define MODEL_PORTRAIT_BITS    0x400000	 /* bit 22    */
#define MODEL_SENSOR_BITS      0x3F0000	 /* bit 16-21 */
#define MODEL_HARDWARE_BITS    0x00FE00	 /* bit 9-15  */
#define MODEL_NEW_ABS_BITS     0x000080	 /* bit 7     */
#define MODEL_CAP_PEN_BITS     0x000040	 /* bit 6     */
#define MODEL_SIMPLE_CMD_BITS  0x000020	 /* bit 5     */
#define MODEL_GEOMETRY_BITS    0x00000F	 /* bit 0-3   */
/* Not used From TPIoCtl.h */
#define MODEL_LOCKTYPE2        0x000010  /* bit 4     */


typedef struct {
  int rot180; 
  int portrait; 
  int sensor; 
  int hardware; 
  int new_abs; 
  int cap_pen; 
  int simple_cmd; 
  int geometry; 
} model_id_type; 

/*
** The sensor types as of STIG 2.5
** Page 11. Plus some guessing.
*/
static char *model_names [] = {
  "Unknown",                                /*  0 */
  "Standard TouchPad (TM41xx134)",
  "Mini Module (TM41xx156)",
  "Super Module (TM41xx180)",
  "Romulan Module",
  "Apple Module",
  "Single Chip",
  "Flexible pad (discontinued)",
  "Ultra-thin Module (TM41xx220)",
  "Wide pad Module (TW41xx230)",
  "TwinPad",                                /* 10 */
  "StampPad Module (TM41xx240)",
  "Sub-mini Module (TM41xx140)",
  "MultiSwitch module (TBD)",
  "Standard Thin",
  "Advanced Technology Pad (TM41xx221)",
  "Ultra-thin Module, connector reversed",
  "Low Power Module",
  "Thin Module, ATP",
  "Snap dome Module, ATP", 
  "FlexArm Module",                         /* 20 */
  "TWIII Module (TouchWriter 3)",
  "TWIII Module (P64)",
  "Combo Module",
  "Squish Module",
  "Thin TTL Serial module",
  "TWIII Ultra Thin Module",
  "PS/2 Passthrough", 
  "4 Button On Board Module",
  "6 Buttons Off Board Module",             
  "6 Buttons On Board Module",              /* 30 */
  "Ultrathin TTL serial Module",
  "ClearPad Module",
  "HyperThin Sensor Module",
  "Pad With Scroll Strip",
  "UltraThin TTL rounded, serial Module",
  "Ultrathin ATP Module",
  "SubMini w/6 buttons",
  "Standard USB Module",
  "cPad Dropin Plain USB Module",
  "cPad",                                   /* 40 */ 
  "Buttom Curved",
  "",
  "",
  "UltraNav",                                            /* mode: 8893 */
};


/*
** Define the information known about a sensor.
** STIG page 14.
** Resolution only apply for absolute mode.
** For older models the default resolution is 85x94.
*/
typedef struct {
  char *model;
  int   x_per_mm;
  int   y_per_mm;
  float width_mm;
  float height_mm;
} sensor_info_type;


typedef struct {
  int Xupmm;
  int Yupmm;
} resolution_type;


static sensor_info_type sensor_info [] = {
  { "Generic values", 85,  94, 47.1, 32.3 },
  { "Standard",       85,  94, 47.1, 32.3 },
  { "Mini",           91, 124, 44.0, 24.5 },
  { "Super",          57,  58, 70.2, 52.4 },
  { "",                0,   0,  0.0,  0.0 },
  { "",                0,   0,  0.0,  0.0 },
  { "",                0,   0,  0.0,  0.0 },
  { "",                0,   0,  0.0,  0.0 },
  { "UltraThin",      85,  94, 47.1, 32.3 },
  { "Wide",           73,  96, 54.8, 31.7 },
  { "",                0,   0,  0.0,  0.0 },
  { "Stamp",         187, 170, 21.4, 17.9 },
  { "SubMini",       122, 167, 32.8, 18.2 },
};

/*
** The information returned in the extended capability packet.
** STIG page 15
*/

#define EXT_CAP_EXTENDED     0x8000     /* Bit 15  */
#define EXT_CAP_EXT_REQUESTS 0x7000     /* Bits 12-14 */
#define EXT_CAP_CMIDDLE      0x0400     /* Bit 10  */
#define EXT_CAP_STICK        0x0080     /* Bit 8   */
#define EXT_CAP_PASS_THROUGH 0X0080     /* Bit 8!! */
#define EXT_CAP_SLEEP        0x0010     /* Bit 4   */
#define EXT_CAP_FOUR_BUTTON  0x0008     /* Bit 3   */
#define EXT_CAP_MULTI_FINGER 0X0002     /* Bit 1   */
#define EXT_CAP_PALM_DETECT  0X0001     /* Bit 0   */
/* These are not used, but guessed from the Synaptics touchpad source code examples. */
#define EXT_CAP_VSCROLL      0x4000     /* Bit 14  */
#define EXT_CAP_HSCROLL      0x2000     /* Bit 13  */
#define EXT_CAP_IBM          0x1000     /* Bit 12  */
#define EXT_CAP_WHEELPAD     0x0200     /* Bit     */


typedef struct {
  int extended; 
  int ext_requests;
  int cmiddle;
  int stick;
  int sleep; 
  int four_button; 
  int multi_finger; 
  int palm_detect; 
} ext_cap_type;

/*
** The information in the mode byte.
** STIG Page 17
*/
#define ABSOLUTE_MODE      0x80      /* Bit 7 set */
#define RELATIVE_MODE      0x00	     /* Bit       */
#define HIGH_REPORT_RATE   0x40	     /* Bit 6 set = 80 packages per second */
#define LOW_REPORT_RATE    0x00	     /* Bit         40 packages per second */
#define USE_9600_BAUD      0x08	     /* Bit 3 for serial protocol */
#define USE_1200_BAUD      0x00	     /* Bit   */
#define PS2_SLEEP          0x08	     /* Bit 3 for ps2 protocol */
#define PS2_NO_SLEEP       0x00	     /* Bit   (only  button press activates touchpad again)*/
#define NO_TAPDRAG_GESTURE 0x04      /* Bit 2 for model version >= 4 */
#define TAPDRAG_GESTURE    0x00      /* Bit   */     
#define EXTENDED_REPORT    0x02	     /* Bit 1 for serial protocol absolute mode only */
#define NORMAL_REPORT      0x00	     /* Bit   */
#define STICK_4_BYTES      0x02	     /* Bit 1 for ps2 protocol this disables any stick attached */
#define STICK_3_BYTES      0x00	     /* Bit          if the stick isn't in special mode */
#define REPORT_W_ON        0x01	     /* Bit 0 set */
#define REPORT_W_OFF       0x00      /* Bit   */


/*
** A location record.
** This is needed to make an average over several packages, 
** because the reported x,y might not be that accurate.
*/
typedef struct {
  int x;
  int y;
} location_type;

/*
** The format of the translated data to a report.
*/
typedef struct {
  unsigned int buttons;
  unsigned int multi_buttons;
  int x;
  int y;
  int pressure;
  int gesture;
  int fingers;
  int fingerwidth;
  int w;
} report_type;

static  report_type last_report;
static  report_type cur_report;

/*
** Touchpad Class. The following "privat" variables should be included into the
** class if this driver is suppose to handle more than one touchpad.
*/

typedef struct {
  int fd;

  identify_type     identify[2];
  model_id_type     model[2];
  ext_cap_type      capabilities[2];  
  sensor_info_type  sensor[2];
  resolution_type   resolution[2];

} tp_class;

static tp_class *TP = NULL;


/*
** Parameters for controlling the touchpad. Not user configurable.
*/
/* touchpad information */
static int           res_x;
static int           res_y;
static int           x_per_mm;
static int           y_per_mm;


/* status information */
static int           packet_num = 0;                /* Holds the number of recieved packets with the current finger on the pad */
static int           was_tap_edges = 0;             /* Which edges was set when the tap was made */
static int           was_non_edge = 0;              /* Boolean. The finger has at on time been in the middle (no edges) */
static int           was_kept_edges = ALL_EDGES;    /* Which edges have been set all the time */
static location_type last_locs [4];                 /* Holds the last 4 reported locations (queue) */
static Gpm_Event     last_state;                    /* The last reported state to gpm */
static int           finger_on_pad_timer = 0;       /* Times whether a tap was to long or slow. */
static int           tap_lower_limit_packet;        /* How many packets is the lowest a tap may last */
static int           tap_upper_limit_packet;        /* How many packets is the higest a tap may last */

/* Action information */
static int           last_corner_action = GPM_B_NOT_SET;
static int           last_finger_action = GPM_B_NOT_SET;
static int           last_normal_buttons_actions[8] = 
  {GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET};
static int           last_stick_button_actions[8]  = 
  {GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET};
static int           last_multi_buttons_actions[8] = 
  {GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET,GPM_B_NOT_SET};

/* toss status information */
static int           is_tossing = 0;                /* Are tossing in progress */
static int           was_tossing = 0;               /* Is a toss completed: preventing a new toss */
static int           min_toss_dist__2 = 32000;      /* How much to move to call it a toss */
static int           max_toss_packets;              /* Toss must start with a finger on the pad at max packets */
static int           min_toss_packets;              /* Toss must start with a finger on the pad at min packets */
static int           prevent_toss_packets;     
static int           toss_timer;
static location_type toss_speed;
static location_type touch_loc;

/* Multi tap information */
static int           gesture_delay = 0;             /* Timer to inhibit buttons so an double tap is made */
static int           fake_forget_tap_interval = 0;  /* if hardware sends tap-hold, we need to keep track */
static int           fake_time_to_forget_tap = 0;   /* not to lose user defined actions in the hold periods. 
						     * (action like: Multifingers, non-repeating actions etc.*/

/* Multi finger information */
static int           was_fingers = 0;               /* How many fingers was on the pad at max */
static int           was_tap_fingers = 0;           /* How many fingers was used to do the tap */
static int           multi_finger_stop_timer = 0;
static int           multi_finger_pressure  = 0;
static int           multi_finger_xy = 0;
static location_type extra_motion;

/* Scrolling information */
static int           is_scrolling = 0;            /* Scrolling either way using touchpad edge. The only
						   * variable that are set to 0 when not scrolling again. */
static int           is_vscrolling = 0;           /* Is vertical scrolling.   Note this is never reset */
static int           is_hscrolling = 0;           /* Is horisontal Scrolling. Note this is never reset */
static int           is_always_scrolling = 0;     /* Only report either scrollingings, no mouse movement */
static int           scrolling_speed_timer = 0;
static int           vscrolling_amount_left = 0;  /* Tells how much to scroll up or down */
static int           hscrolling_amount_left = 0;  /* Tells how much to scroll left or right */

/* Multi button support */
static int           multi_buttons_no = 0;

/* Guest (stick) information */
static int           stick_mode = 0;              /* What protocol (ps,imps,exps) is the stick in */
static int           stick_uses_4_bytes = 0;      /* Does the stick protocol use 3 or 4 bytes */


/****************************************************************************
**
** ROUTINES for processing either type of touchpad.
**
****************************************************************************/

/*
** Dump the report data for debugging.
**
** Because the synaptics sends (trivial) data in one second after last touch,
** which makes reading the debug data harder, only dump the report if it is different 
** than the previously dumped.
*/
static void tp_dump_report_data (report_type report,
				 int edges,
				 Gpm_Event* state) 
{
  static report_type  last_report_reported;
  static unsigned int times_report_repeated = 0;

  report.buttons |= state->buttons;

  if (!report.fingers && memcmp(&report,&last_report_reported,sizeof(report_type)) == 0){
    times_report_repeated++;
    return;
  }

# ifdef DEBUG_REPORTS_NO_GARBAGE
  if (report.pressure < 11 && 
      !report.gesture && !gesture_delay && !last_report_reported.gesture &&
      !report.buttons && !report.multi_buttons) {
    times_report_repeated++;
    return;
  }
# endif
  
  /* Was the last report repeated ? */
  if(times_report_repeated > 0){
    gpm_report (GPM_PR_DEBUG,"\rSynps2: Last report reported %d times\n",times_report_repeated); 
    times_report_repeated = 0;
  }

  last_report_reported = report;

  gpm_report (GPM_PR_DEBUG,
	      "\rSynps2: %c%c%c%c%c%c%c%c%c%c  %4dx%-4d %3d %2d %d  %c%c%c%c  %c%c%c    %3d%3d %d    %c",
	      report.fingers ? 'f' : '-',  
	      report.gesture ? 'g' : '-',
	      
	      report.buttons & LEFT_BUTTON     ? 'l' : '-',
	      report.buttons & MIDDLE_BUTTON   ? 'm' : '-',
	      report.buttons & RIGHT_BUTTON    ? 'r' : '-',
	      report.buttons & FOURTH_BUTTON   ? 'f' : '-',
	      report.buttons & UP_BUTTON       ? 'u' : '-',
	      report.buttons & DOWN_BUTTON     ? 'd' : '-',
	      report.buttons & B_SCROLL_BUTTON ? 'b' : '-',
	      report.buttons & F_SCROLL_BUTTON ? 'f' : '-',

	      report.x, report.y, report.pressure,
	      report.w,was_fingers, 
	      
	      edges & LEFT_EDGE   ? 'l' : '-',
	      edges & RIGHT_EDGE  ? 'r' : '-',
	      edges & BOTTOM_EDGE ? 'b' : '-',
	      edges & TOP_EDGE    ? 't' : '-',
	      
	      report.gesture && !report.fingers ? 't' : '-',
	      report.gesture && report.fingers  ? 'd' : '-',
	      is_scrolling                      ? 's' : '-',
	      
	      state->dx,state->dy,state->buttons,

	      (multi_finger_pressure>4500 && multi_finger_xy>50000? 'f':' '));

}


/* syn_dump_info
**
** Print properties for the hardware.
**
 */
static void syn_dump_info(tp_class *tp,int stick)
{
  identify_type     identify     = tp->identify[stick];
  model_id_type     model        = tp->model[stick];
  ext_cap_type      capabilities = tp->capabilities[stick];  
  resolution_type   resolution   = tp->resolution[stick];
  sensor_info_type  sensor       = tp->sensor[stick];
  

  gpm_report (GPM_PR_INFO,"Synaptic %s Device:",(stick?"Stick":"Touchpad"));
    
  gpm_report (GPM_PR_INFO,"Synaptics Ident:  model_code=%d   Firmware version %d.%d",
	      identify.model_code, identify.major, identify.minor);
  gpm_report (GPM_PR_INFO,"Synaptics model:");
  gpm_report (GPM_PR_INFO,"   rot180:       %s", model.rot180 ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   portrait:     %s", model.portrait ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   sensor:       %d", model.sensor);
  gpm_report (GPM_PR_INFO,"                 %s", syn_model_name (model.sensor));
  gpm_report (GPM_PR_INFO,"                 %s", sensor.model);
  gpm_report (GPM_PR_INFO,"                 %dx%d res/mm",   sensor.x_per_mm, sensor.y_per_mm);
  gpm_report (GPM_PR_INFO,"                 %4.1fx%4.1f mm", sensor.width_mm, sensor.height_mm);
  gpm_report (GPM_PR_INFO,"                 %dx%d res", res_x, res_y);
  gpm_report (GPM_PR_INFO,"   hw res/mm     %dx%d res/mm",   resolution.Xupmm, resolution.Yupmm);
  gpm_report (GPM_PR_INFO,"   hardware:     %d", model.hardware);
  gpm_report (GPM_PR_INFO,"   newABS:       %s", model.new_abs ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   simpleCmd:    %s", model.simple_cmd ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   geometry:     %d", model.geometry);
  gpm_report (GPM_PR_INFO,"   extended:     %s", capabilities.extended ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   stick:        %s", capabilities.stick ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   sleep:        %s", capabilities.sleep ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   4 buttons:    %s", capabilities.four_button ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   middlebutton: %s", capabilities.cmiddle ? "Yes" : "No");  
  gpm_report (GPM_PR_INFO,"   ext. buttons: %s (%d)", capabilities.ext_requests ? "Yes" : "No",multi_buttons_no);
  gpm_report (GPM_PR_INFO,"   multifinger:  %s", capabilities.multi_finger ? "Yes" : "No");
  gpm_report (GPM_PR_INFO,"   palmdetect:   %s", capabilities.palm_detect ? "Yes" : "No");

#if DEBUG_TOSS
  gpm_report (GPM_PR_INFO,"Min Toss Dist^2: %d\n", min_toss_dist__2);
#endif
}

/*
 * tp_allocate_tp_class sets up a touchpad class
*/
static tp_class* tp_allocate_tp_class(fd) {
  tp_class *ret = TP;

  gpm_report(GPM_PR_DEBUG,"Allocating Synaptics PS/2 TouchPad class");

  /* Allocate touchpad class*/
  if (!ret) 
    ret = malloc(sizeof(*ret));
 
  if (!ret)
    gpm_report(GPM_PR_OOPS,"Allocating Touchpad class failed");    

  memset(ret,0,sizeof(*ret));
  ret->fd = fd;

  return ret;
}


/* Get model name, STIG page 11 */
static char *syn_model_name (int sensor) 
{
  if (sensor < 0 || sensor >= sizeof(model_names)/sizeof(*model_names)) {
    return "Reserved (Unknown)";
  } else {
    return model_names [sensor];
  }
}

/* convert the model id from bits to values 
*  STIG page 11 
*/
static void syn_extract_model_id_info (model_id_type *model, int model_int) 
{
  model->rot180     = check_bits (model_int,  MODEL_ROT180_BITS);
  model->portrait   = check_bits (model_int,  MODEL_PORTRAIT_BITS);
  model->sensor     =            (model_int & MODEL_SENSOR_BITS)   >> 16;
  model->hardware   =            (model_int & MODEL_HARDWARE_BITS) >> 8;
  model->new_abs    = check_bits (model_int,  MODEL_NEW_ABS_BITS);
  model->cap_pen    = check_bits (model_int,  MODEL_CAP_PEN_BITS);
  model->simple_cmd = check_bits (model_int,  MODEL_SIMPLE_CMD_BITS);
  model->geometry   =            (model_int & MODEL_GEOMETRY_BITS);
}


/* Translate the reported data into a record for processing. If
 * the information is missing use the default informations. 
 * If the device did provide resolution use these instead.
 *  STIG page 14*/
static void syn_set_sensor_info (tp_class *tp, int stick) 
{
  int sensor_id = tp->model[stick].sensor;

  if (sensor_id > 0 && sensor_id < sizeof(sensor_info)/sizeof(*sensor_info) &&
      sensor_info[sensor_id].x_per_mm != 0 ) {
    tp->sensor[stick] = sensor_info [sensor_id];
  } else {
    tp->sensor[stick] = sensor_info[0];

    if (tp->resolution[stick].Xupmm && tp->resolution[stick].Yupmm) {
      tp->sensor[stick].x_per_mm = tp->resolution[stick].Xupmm;
      tp->sensor[stick].y_per_mm = tp->resolution[stick].Yupmm;
    }else
      gpm_report(GPM_PR_INFO,"Synaptics %s Device sensor info %d is unknown:"
		 "using generic values",stick?"stick":"touchpad",sensor_id);
  }
}


/* Translate the reported data of extended capabilities STIG page 15.  If the
 extended bit is not set it should be assumed that neither of the other
 capabilities is available. Some of the capabilities are found in additional
 requests */
static void syn_extract_extended_capabilities(ext_cap_type *cap, int ext_cap_int)
{

# ifdef DEBUG  
  gpm_report(GPM_PR_INFO,"Synaptics Device Capabilities: %02X",ext_cap_int);
# endif 

  cap->extended       = check_bits (ext_cap_int, EXT_CAP_EXTENDED);

  if(cap->extended){
    cap->ext_requests =            (ext_cap_int & EXT_CAP_EXT_REQUESTS) >> 12;
    cap->cmiddle      = check_bits (ext_cap_int, EXT_CAP_CMIDDLE);
    cap->stick        = check_bits (ext_cap_int, EXT_CAP_STICK);
    cap->sleep        = check_bits (ext_cap_int, EXT_CAP_SLEEP);
    cap->four_button  = check_bits (ext_cap_int, EXT_CAP_FOUR_BUTTON);
    cap->multi_finger = check_bits (ext_cap_int, EXT_CAP_MULTI_FINGER);
    cap->palm_detect  = check_bits (ext_cap_int, EXT_CAP_PALM_DETECT);
  }else{
    cap->ext_requests = 0;
    cap->cmiddle      = 0;
    cap->stick        = 0;
    cap->sleep        = 0;
    cap->four_button  = 0;
    cap->multi_finger = 0;
    cap->palm_detect  = 0;
    /* Wmode is not supported, but this should be turned off after reading the
       config file.*/
  }
}


/*
** Check for edges.
*/
static int tp_edges (location_type loc) 
{
  int edges = 0;
 
  if (loc.x <= x_min_center)
    edges |= LEFT_EDGE;

  if (loc.x >= x_max_center)
    edges |= RIGHT_EDGE;

  if (loc.y <= y_min_center) {
    if (!y_is_reversed)
      edges |= BOTTOM_EDGE;
    else
      edges |= TOP_EDGE;
  }

  if (loc.y >= y_max_center) {
    if (!y_is_reversed)
      edges |= TOP_EDGE;
    else
      edges |= BOTTOM_EDGE;
  }

  return edges;
}


/*
** Check if a corner has a corner action. (The index is fixed to help the
** configurationreading)
*/

static inline int is_corner_unused(int edges)
{
  int index;

  switch(edges) {
  case TOP_EDGE   |LEFT_EDGE:  index = 0; break;
  case BOTTOM_EDGE|LEFT_EDGE:  index = 1; break;
  case TOP_EDGE   |RIGHT_EDGE: index = 2; break;
  case BOTTOM_EDGE|RIGHT_EDGE: index = 3; break;
  default: return 0;
  }
  return corner_actions[index].action == No_Action;
}


/*
** Calculate the motion step using the last x reported positions.
*/
# define fx(no) (last_locs [mod4 (packet_num - (no))].x)
# define fy(no) (last_locs [mod4 (packet_num - (no))].y)

static inline location_type tp_calc_motion (float speed_factor) 
{
  location_type ret_motion;
  static float frac_dx,frac_dy;
  float dx,dy;

  switch (motion_method) {
  case 2:
    dy = fy(0) - fy(1);
    dx = fx(0) - fx(1);
    break;
  default:
    gpm_report (GPM_PR_WARN,"Wrong motion_method setting %d: " 
		"Now calculating the motion using 3 packets",motion_method);
    motion_method = 3;
  case 3:
    dy = ((fy(0) - fy(1)) / 2.0) + ((fy(1) - fy(2)) / 2.0);
    dx = ((fx(0) - fx(1)) / 2.0) + ((fx(1) - fx(2)) / 2.0);
    break;
  case 4:
    dy = ((fy(0) - fy(1))*3.0/4.0) + ((fy(1) - fy(2))*1.0/4.0);
    dx = ((fx(0) - fx(1))*3.0/4.0) + ((fx(1) - fx(2))*1.0/4.0);
    break;
  case 5:
    dy = ((fy(0) - fy(1)) / 3.0) + ((fy(1) - fy(2)) / 3.0) + ((fy(2) - fy(3)) / 3.0);
    dx = ((fx(0) - fx(1)) / 3.0) + ((fx(1) - fx(2)) / 3.0) + ((fx(2) - fx(3)) / 3.0);
    break;
  case 6:
    dy = ( 3.0 * fy(0) - 4.0 * fy(1) + fy(2)) / 2.0;
    dx = ( 3.0 * fx(0) - 4.0 * fx(1) + fx(2)) / 2.0;
    break;
  }

  if (!fraction_motion_enabled) {
    frac_dx = 0.0;
    frac_dy = 0.0;
  }

  dx = speed_factor * dx + frac_dx;
  dy = speed_factor * dy + frac_dy;
  ret_motion.x = dx; 
  ret_motion.y = dy; 
  frac_dx = dx - ret_motion.x;
  frac_dy = dy - ret_motion.y;

  if (y_is_reversed)
    ret_motion.y = -ret_motion.y; 

  return ret_motion;
}

#undef fx
#undef fy

/**
 ** Handle scrolling. The wheel is way too fast to be usefull, so only report
 ** every scrolling_speed. This function is also called when scrolling is done by
 ** buttons, which is why DOWN and UP must be removed in the case of not scrolling.
 */
static void tp_handle_scrolling(Gpm_Event *state)
{
  state->buttons &= ~(GPM_B_DOWN | GPM_B_UP | GPM_B_BACKWARD | GPM_B_FORWARD);
  
  /* Limit the amount of scrolling, so that we do not overrun. Limited to be
     within the 80 (1 sec) packet */
  if ( vscrolling_amount_left >  scrolling_speed*80 )
    vscrolling_amount_left = scrolling_speed*80;

  if ( vscrolling_amount_left < -scrolling_speed*80 )
    vscrolling_amount_left = -scrolling_speed*80;

  if ( hscrolling_amount_left >  scrolling_speed*80 )
    hscrolling_amount_left = scrolling_speed*80;

  if ( hscrolling_amount_left < -scrolling_speed*80 )
    hscrolling_amount_left = -scrolling_speed*80;

  /* Handle the actually scrolling. */
  if (vscrolling_amount_left > scrolling_speed){
    vscrolling_amount_left -= scrolling_speed;
    state->buttons |= GPM_B_UP;
  }else if (vscrolling_amount_left < -scrolling_speed){
    vscrolling_amount_left += scrolling_speed;
    state->buttons |= GPM_B_DOWN;
  } 

  if (hscrolling_amount_left > scrolling_speed){
    hscrolling_amount_left -= scrolling_speed;
    state->buttons |= GPM_B_BACKWARD;
  }else if (hscrolling_amount_left < -scrolling_speed){
    hscrolling_amount_left += scrolling_speed;
    state->buttons |= GPM_B_FORWARD;
  }
}


/*
** process_action
**
** Do the action and return a button state for a given action list and mask.
** Actions that should not be repeated should report GPM_B_NOT_SET (when
** holding down a button).
*/
static int tp_process_action(touchpad_action_type *action_list, int mask)
{
  int i = 0;
  int status = GPM_B_NOT_SET;
  static int Left_Double_Click = 0, Right_Double_Click = 0, Middle_Double_Click = 0;

  if (mask == 0){
    gpm_report (GPM_PR_WARN,"Action Mask is 0");
    return GPM_B_NOT_SET;
  }
  
  while (action_list [i].action_mask) {
    if (check_bits (mask, action_list [i].action_mask)) {
      switch (action_list [i].action) {
      case Left_Button_Action:
	status = GPM_B_LEFT;
	break;
      case Middle_Button_Action:
	status = GPM_B_MIDDLE;
	break;
      case Right_Button_Action:
	status = GPM_B_RIGHT;
	break;
      case Fourth_Button_Action:
	status = GPM_B_FOURTH;
	break;
      case Up_Scrolling_Action:
	vscrolling_amount_left += ceil(scrolling_button_factor * scrolling_speed);
	status = GPM_B_UP;
	break;
      case Down_Scrolling_Action:
	vscrolling_amount_left -= ceil(scrolling_button_factor * scrolling_speed);
	status = GPM_B_DOWN;
	break;
      case Left_Scrolling_Action:
	hscrolling_amount_left += ceil(scrolling_button_factor * scrolling_speed);
	status = GPM_B_BACKWARD;
	break;
      case Right_Scrolling_Action:
	hscrolling_amount_left -= ceil(scrolling_button_factor * scrolling_speed);
	status = GPM_B_FORWARD;
	break;
      case Left_Double_Click_Action:
	Left_Double_Click++;
	status = GPM_B_LEFT; 
	if(Left_Double_Click == 2)
	  status = GPM_B_NONE; 
	if(Left_Double_Click == 4) {
	  status = GPM_B_NOT_SET; 
	  Left_Double_Click = 0;
	}
	break;
      case Right_Double_Click_Action:
	Right_Double_Click++;
	status = GPM_B_RIGHT; 
	if(Right_Double_Click == 2)
	  status = GPM_B_NONE; 
	if(Right_Double_Click == 4) {
	  status = GPM_B_NOT_SET; 
	  Right_Double_Click = 0;
	}
	break;
      case Middle_Double_Click_Action:
	Middle_Double_Click++;
	status = GPM_B_MIDDLE; 
	if(Middle_Double_Click == 2)
	  status = GPM_B_NONE; 
	if(Middle_Double_Click == 4) {
	  status = GPM_B_NOT_SET; 
	  Middle_Double_Click = 0;
	}
	break;
      case Turn_On_Off_Action:
	touchpad_enabled = !touchpad_enabled;
	status = GPM_B_NOT_SET;
	break;
      case Debug_On_Off_Action:
#       ifdef DEBUG_SYNAPTIC
	  debug_syn_to_stderr = !debug_syn_to_stderr;
#       endif
	status = GPM_B_NOT_SET;
	break;
      case Reset_Touchpad_Action:
	syn_ps2_reset(which_mouse->fd);
	syn_ps2_absolute_mode(which_mouse->fd);
	status = GPM_B_NOT_SET;
	break;
      case Toggle_Multi_Button_Action:
	multi_buttons_is_mouse = !multi_buttons_is_mouse;
	status = GPM_B_NOT_SET;
	break;
      case Toggle_Stick_Pressure_Action:
	stick_pressure_enabled = !stick_pressure_enabled;
	status = GPM_B_NOT_SET;
	break;
      case Toggle_Stick_Scrolling_Action:
	stick_scrolling_enabled = !stick_scrolling_enabled;
	status = GPM_B_NOT_SET;
	break;
      case Toggle_Scrolling_Action:
	is_always_scrolling = !is_always_scrolling;
	status = GPM_B_NOT_SET;
	break;
      case Toggle_Buttons_Action:
	touchpad_buttons_enabled = !touchpad_buttons_enabled;
	status = GPM_B_NOT_SET;
	break;
      case Mouse_Left_Action:
	extra_motion.x = -multi_buttons_mouse_speed;
	status = GPM_B_NONE;
	break;
      case Mouse_Right_Action:
	extra_motion.x =  multi_buttons_mouse_speed;
	status = GPM_B_NONE;
	break;
      case Mouse_Up_Action:
	if (!y_is_reversed)
	  extra_motion.y = -multi_buttons_mouse_speed;
	else
	  extra_motion.y =  multi_buttons_mouse_speed;
	status = GPM_B_NONE;
	break;
      case Mouse_Down_Action:
	if (!y_is_reversed)
	  extra_motion.y =  multi_buttons_mouse_speed;
	else
	  extra_motion.y = -multi_buttons_mouse_speed;
	status = GPM_B_NONE;
	break;
      case No_Action:
	status = GPM_B_NOT_SET;
	break;
      default:
	gpm_report (GPM_PR_WARN,"Default Action: Action no. %X not defined",
		    action_list [i].action); 
	status = GPM_B_NOT_SET;
	break;
      }

      return status;
    }
    
    i++;
  }
  
  return status;
}


/*
** tp_process_corner_taps. 
**
** last_*_action is an easy way of remember which taps/buttons should call/repeat
** the action when the tap/button is held pressed. It could just as well  have
** been one variable where each bit held the repeater info for all the
** tap/buttons, but that will mean more code.
**
** The reason for not actually reusing last_*_action, instead of
** calculating it again, is that some actions like scrolling have "side effects".
** This may be perceived as a design fault.
**
** tp_process_corner_taps and tp_process_button_press are more complicated than
** to what is obvious. This is because 1) in a corner tap-and-hold the hold
** could take place at non edge. 2) A normal tap-and-hold can be moved to a
** corner.
*/
static void tp_process_corner_taps (Gpm_Event *state, report_type *report) 
{
  static int edges_at_cornertap_time;

  if (report->gesture && 
      ((is_corner (was_tap_edges) && !last_report.gesture) || 
       (last_corner_action != GPM_B_NOT_SET))) {

    if(!last_report.gesture && last_corner_action == GPM_B_NOT_SET)
      edges_at_cornertap_time = was_tap_edges;

    last_corner_action = tp_process_action(corner_actions,edges_at_cornertap_time);

    if (last_corner_action != GPM_B_NOT_SET)
      state->buttons |= last_corner_action;
  } 
}

/* tp_process_button_press
**
** Handles finger taps. Same way as tp_process_corner_taps.
**
** Should only calculate a tap if 
** 1) There is a gesture (tap)
** 2) The tap did not start at a corner with a corneraction
** 3) If it is calculated before it should have returned a repeating action.
*/
static void tp_process_finger_taps (Gpm_Event *state, report_type *report) 
{
  int tmp_fingers;

  if (report->gesture && last_corner_action == GPM_B_NOT_SET &&
      !(corner_taps_enabled && is_corner (was_tap_edges) && !last_report.gesture) &&
      !(last_report.gesture && last_finger_action == GPM_B_NOT_SET) ){

    switch (multi_finger_tap_behavior){
    case MULTI_FINGER_TAP_REAL:
      tmp_fingers = (report->fingers ? report->fingers : was_tap_fingers);
      break;
    case MULTI_FINGER_TAP_NO_DEC:
      tmp_fingers = was_fingers;
      break;
    default:
      gpm_report (GPM_PR_WARN,"Default multifinger tap behavior %X is not defined",
		  multi_finger_tap_behavior); 
      multi_finger_tap_behavior = MULTI_FINGER_TAP_STATIC;
    case MULTI_FINGER_TAP_STATIC:
      tmp_fingers = was_tap_fingers;
      break;
    }

    if ( ( multi_finger_tap_enabled && tmp_fingers <= 1) ||
	 (!multi_finger_tap_enabled && tmp_fingers > 0))
      last_finger_action = tp_process_action(multi_finger_actions, ONE_FINGER);
    
    if (multi_finger_tap_enabled && tmp_fingers == 2)
      last_finger_action = tp_process_action(multi_finger_actions, TWO_FINGERS);
    
    if (multi_finger_tap_enabled && tmp_fingers == 3)
      last_finger_action = tp_process_action(multi_finger_actions, THREE_FINGERS);
    
    if (last_finger_action != GPM_B_NOT_SET) 
      state->buttons |= last_finger_action;
  }
}

/*
** tp_process_do_repeating_action
**
** The function test whether a feature's action should be repeated/called.  The
** action should be called if the feature is there and if it was there last
** time, then it should be a repeating action.
*/

static inline void 
tp_process_repeating_action(Gpm_Event *state, int feature, int last_feature, int *last_action, 
			    touchpad_action_type *action_list,int feature_mask)
{
  if (feature) {
    if (!(last_feature && *last_action == GPM_B_NOT_SET)) {
      *last_action = tp_process_action(action_list, feature_mask);
      if (*last_action != GPM_B_NOT_SET)
	state->buttons |= *last_action;
    }
  } else {
      *last_action = GPM_B_NOT_SET;
  }
}

/*
** tp_process_do_repeating_actions
**
** Call tp_process_do_repeating_action foreach possible action (right now only
** buttons, and guest buttons.)
*/

static inline void 
tp_process_repeating_actions(Gpm_Event *state, int features, int last_features, int last_actions[], 
			     touchpad_action_type *action_list)
{
  int feature_no, feature_mask;

  for(feature_no = 0; feature_no < 8; feature_no++) {
    feature_mask = (1<<feature_no);
    tp_process_repeating_action(state, features & feature_mask, last_features & feature_mask,
				&last_actions[feature_no], action_list, feature_mask);
  }
}

/* tp_process_button_press
**
** Handles normal button presses and multi buttons. 
*/
static void tp_process_button_press (Gpm_Event *state, report_type *report) 
{

  if (multi_buttons_enabled && report->multi_buttons) 
    tp_process_repeating_actions(state,report->multi_buttons,last_report.multi_buttons,
				 &last_multi_buttons_actions[0],
				 multi_buttons_is_mouse?multi_buttons_mouse:multi_buttons_actions); 
  
  if (touchpad_buttons_enabled && report->buttons)
    tp_process_repeating_actions(state,report->buttons,last_report.buttons,
				 &last_normal_buttons_actions[0],normal_button_actions); 
  
}


/*
** syn_process_wmode_report
**
** Translate synaptics specific values.
*/


static void syn_process_wmode_report(tp_class *tp, report_type *report )
{
  /* STIG page 9: Values of w, vary from pad to pad. It is not precise when pressure is small < 25.
   * 4-7  finger of normal width
   * 8-14 very wide finger or palm
   * 15   maximum reportable width     
   */
  report->fingerwidth = max(0,report->w - 4);

  /* Check whether there is a finger on the pad or continue to report a finger */
  report->fingers  = ((report->pressure > finger_threshold) ||
		      (last_report.fingers > 0 && report->pressure > finger_off_threshold));
  
  /* use w values. w = 0: 2 fingers, w = 1: 3 fingers, (if there is pressure) */
  if (tp->capabilities[0].multi_finger){      
    if (report->pressure != 0 && (report->w == 0 || report->w == 1)){
      report->fingers = 2+report->w;
    }
  }
}



/*
** syn_ps2_process_extended_packets
**
** The internal synaptics ps2 touchpads can have extra devices attached (stick)
** to them, and they reports their state as special embedded packets. These
** attached devices are handled here.
**
** The function returns 1 if it does not make sense to continue normal touchpad
** processing, 0 otherwise.
**            
** I dont think that the external touchpads (serial) can have the same devices
** attached.  This code should be moved to ps2 specific part, but it is easy to
** have it here.
*/

static int syn_ps2_process_extended_packets( unsigned char *data, 
					     report_type *report,
					     Gpm_Event *state)
{
  static int last_stick_buttons = GPM_B_NONE;
  int tmp_buttons = GPM_B_NONE;

  if (report->w != 3) {
    /* Multiplexing with the stick (guest) device. Assuming that the same
       buttons are hold as last time guest reported. */
    tp_process_repeating_actions(state,last_stick_buttons,last_stick_buttons,
				 &last_stick_button_actions[0],stick_actions);
    return 0;
  }

  /* It cannot be processed further than here as the pressure is 0, which would
   * break things, if the user uses it simultaneously with the touchpad.  Allow
   * some simultaneously usage: tap-hold on touchpad, with extended movement.*/
  tp_process_button_press (state,report);
  if (last_report.gesture) {
    report->gesture = 1;
    if (tap_gesture_enabled) tp_process_finger_taps (state, report);
    if (corner_taps_enabled) tp_process_corner_taps (state, report);
  }
  
  /* Stick invariant bits (I hope). See absolute packets */
  /* Stick pressed: The stick do only generates one packet, so double tap is
   * a problem; squeezing in a non-clicked state. It is probably not a real
   * problem, as it is hard not to move the stick a little between pressing,
   * thereby returning a non-clicked state between the packets. Maybe
   * press-lock mechanism is useful. Forget it, the styk supports it! */
  /* The Synaptic stick (guest) can send encapsulated imps2
   * protocol. (actually any 3/4 bytes protocol) */
  if((data[0] & 0xFC) == 0x84 && 
     (data[1] & 0xC8) == 0x08 &&
     (data[3] & 0xFC) == 0xC4){
    
    if (stick_motion_enabled) {
      state->dx=  ((data[1] & 0x10) ? data[4]-256 : data[4]) * stick_motion_factor;
      state->dy= -((data[1] & 0x20) ? data[5]-256 : data[5]) * stick_motion_factor;
    }
    
    if (stick_pressure_enabled) {
      tmp_buttons  = ((data[1] & 0x01) ? STICK_LEFT_BUTTON   : 0);
      tmp_buttons |= ((data[1] & 0x04) ? STICK_MIDDLE_BUTTON : 0);
      tmp_buttons |= ((data[1] & 0x02) ? STICK_RIGHT_BUTTON  : 0);
      if (stick_mode == 3) {
	tmp_buttons |= ((data[2] & 0x10) ? STICK_FOURTH_BUTTON : 0);
	tmp_buttons |= ((data[2] & 0x20) ? STICK_FIFTH_BUTTON  : 0);	  
      }
      tp_process_repeating_actions(state,tmp_buttons,last_stick_buttons,
				   &last_stick_button_actions[0],stick_actions);
    } 
    
    if (stick_mode != 0 && (data[2] & 0x07)) 
      vscrolling_amount_left += (signed int)(data[2] & 0x07);

    if (stick_scrolling_enabled) {
      vscrolling_amount_left -= state->dy;
      hscrolling_amount_left += state->dx;
      state->dx = 0;
      state->dy = 0;
    }
    
    tp_handle_scrolling(state);
    
    last_stick_buttons = tmp_buttons;
    
#   ifdef DEBUG_STICK      
    gpm_report (GPM_PR_DEBUG,"StickData? %02x %02x %02x %02x %02x %02x    dx:%3d  dy:%3d  b:%2d",
		data[0],data[1],data[2],data[3],data[4],data[5],
		state->dx,state->dy,state->buttons);
#   endif
    return 1;
  }
  
  /* This is unknown packet. */
  gpm_report (GPM_PR_ERR,"\rSynps2: w is 3 but is not a stick? "
	      "Data: %02X %02X %02X %02X %02X %02X",
	      data [0],data [1],data [2],data [3],data [4],data [5]);
  return 1;

}

/**
** Preprocess the report even before doing wmode stuff.
** Is always called directly after the conversion to check the data received.
** return 0 if it is reasonable, 1 if there is something wrong.
**
** Checks for correct data package, palm on the pad, number of fingers.
**            
*/

static int tp_find_fingers ( report_type *report,
			     Gpm_Event *state)
{

  static int fake_extra_finger;
  static int was_fake_pressure;
  static int was_touchpad_motion_enabled;

  /* Check whether there is a palm on the pad */
  if (palm_detect_enabled &&  
      report->fingers && (report->fingerwidth >= palm_detect_level)){
#   ifdef DEBUG_PALM
    gpm_report (GPM_PR_DEBUG,"\rTouchpad: palm detected. Finger width: %d",report->fingerwidth); 
#   endif
    /* BUG should not return 1, as this drops packets. Return a repeated report ? */
    /*       last_locs [mod4 (packet_num - 1)].x = report->x; */
    /*       last_locs [mod4 (packet_num - 1)].y = report->y; */
    /*     or */
    /*       report->pressure = 0; */
    return 1;
  }


  /* Extra check for vertical multi fingers which my pad is very bad to detect.
  ** Only check for extra fingers if no of fingers has not changed.  Faking
  ** fingers may go wrong so sanity check needed.  This is not an attempt to
  ** know the number of fingers all the time, as this is not needed. 
  */
  if (fake_finger_layer_enabled){
    
    if (report->fingers > 1){
      fake_extra_finger = 0;
    }
        
    if(report->fingers == 0){
      fake_extra_finger = 0;
#     ifdef DEBUG_REPORTS
        multi_finger_pressure  = 0;
        multi_finger_xy = 0;
#     endif
    }else
      if (report->fingers + fake_extra_finger == last_report.fingers) {

	multi_finger_pressure = sqr(report->pressure) - sqr(last_report.pressure);
	multi_finger_xy = (sqr(last_locs [mod4 (packet_num - 1)].x - report->x) +
			   sqr(last_locs [mod4 (packet_num - 1)].y - report->y) );

	/* Check for a second finger. If the move is larger than tap range,
	* then it is not a tap, and adding the finger does not change reported        
	* buttons, but it will still stop the moving if
	* multi_finger_stop_enabled is on. These tests are complete base on my
	* imagination and experience, so any better idea are welcome. */
	if(report->fingers == 1 && 
	   multi_finger_pressure > 4500 && multi_finger_xy > 2*sqr((double)tap_range)){
	    fake_extra_finger = 1;
	    was_fake_pressure =  report->pressure;

	}/* Check for third finger. */
	else if(last_report.pressure  > 180  && (report->fingers + fake_extra_finger) == 2 &&
		 multi_finger_pressure > 4500 && multi_finger_xy > 2*sqr((double)tap_range)){
	  fake_extra_finger = 3 - report->fingers;	    
	  was_fake_pressure = report->pressure;

	} else if ( (fake_extra_finger > 0) &&
		    (was_fake_pressure - report->pressure > 20)){
	  fake_extra_finger --;

	} else if(multi_finger_pressure < -5000 && multi_finger_xy > 2*sqr((double)tap_range)){
	  /* Probably missed a placed multi finger, as this is one removed! */
	  last_report.fingers ++;
	}
      }
    
    report->fingers += fake_extra_finger;
    
  }


  /* Check whether to reduce sensibility when adding or removing fingers */
  if (multi_finger_stop_enabled){

    /* Restore motion, but only if nothing already has turn it on. */
    if (multi_finger_stop_timer == 1 && !touchpad_motion_enabled) 
      touchpad_motion_enabled = was_touchpad_motion_enabled;

    /* Is a finger added or removed since last packet? */
    if ( (report->fingers     > 1 && report->fingers > last_report.fingers) ||
	 (last_report.fingers > 1 && report->fingers < last_report.fingers) ){

      if (multi_finger_stop_timer <= 1) {
	was_touchpad_motion_enabled = touchpad_motion_enabled;
	touchpad_motion_enabled = 0;
      }

      if (multi_finger_stop_timer == 0) {
	extra_motion.x -= last_state.dx;
	extra_motion.y -= last_state.dy;
      }

      multi_finger_stop_timer = multi_finger_stop_delay;

#     ifdef DEBUG_MULTI_FINGER
      gpm_report (GPM_PR_DEBUG,"%s multi finger last:%d  new:%d fake:%d",
		  report->fingers > last_report.fingers ? "Add":"Remove",
		  last_report.fingers,report->fingers,fake_extra_finger);
#     endif
    } 

    if (multi_finger_stop_timer > 0) 
      multi_finger_stop_timer --;
  }

  /* If wmode is not used, we do not know all the informations to reset the
   * was_fingers variable or stop waiting for a tap-hold, so set a timer to
   * reset the variables when we believe it is time.  pads_tap_interval is
   * optimal when set to the touchpads tap_interval.
   */

  if(fake_forget_tap_interval){
    if (report->fingers || report->gesture)
      fake_time_to_forget_tap = pads_tap_interval;

    if(fake_time_to_forget_tap > 0)
      fake_time_to_forget_tap --;
    else {
      was_fingers = 0;
      was_tap_fingers = 0;
    }
  }


  was_fingers = max(was_fingers,report->fingers);

  return 0;
}


/** 
** tp_find_gestures
** Process the report from a wmode enabled device.  No gesture calculation is
** done by the device in wmode, so the find tap and drag hold and tap hold gestures.
**
** Tap mechanism: High when touched. (=: assigned, -: decreased, +: increased)
**
**                         _________________________  
**                         | tap_lower/upper_limit |tap_interval|  
**         -----------------                       -----------------
** 
** stroke_x/y         :    =                         
** finger_on_pad_timer: 0000123        +++++++++++++0000000000000000
** time_to_forget_tap : 0000000        000000000000=---------3210000
** gesture_delay      : 0000000  ...   00000000000000000000000000000
** drag_locked        : 0000000        00000000000000000000000000000
** gesture            : 0000000        00000000000011111111111110000
**
** Note:
**  0) When gesture is high a button is reported pressed (usually left button).
**  1) finger_on_pad_timer is increased at most to 1 larger than tap_upper_limit,
**     but then it is not a tap any longer.
**  2) If tap_interval is larger than 1s (80 packets) then time_to_forget_tap never reaches 0,
**     before the touchpads stop sending packets, so gesture is reported until touched again.  
**
**
** Tap hold mechanism: High when touched. (=: assigned, -: decreased, +: increased)
**
**                                       tap_interval
**                         ________________  v ___   ___
**                         |tap_l/u_limit |    |not tap|
**         -----------------              ------       --------    
** 
** stroke_x/y         :    =                   =      
** finger_on_pad_timer: 0000123+       +++00000123++  +++0000
** time_to_forget_tap : 00000000       000=----11111  1110000
** gesture_delay      : 00000000 ...   0000000000000  0000000
** drag_locked        : 00000000       0000000000000  0000000
** gesture            : 00000000       0001111111111  1110000
**
** Note:
**  0) time_to_forget_tap is set to 1 when touching the pad 2 time, so the gesture is stop when released. 
**     (two fast taps would be a problem).
**
**
** Two consecutive taps mechanism: High when touched. (=: assigned, -: decreased, +: increased)
**
**                                       tap_interval  multiple_tap_delay
**                         ________________  v _______________  v   |
**                         |tap_l/u_limit |    |tap_l/u_limit|  
**         -----------------              ------             --------    
** 
** stroke_x/y         :    =                   =      
** finger_on_pad_timer: 0000123+       +++00000123++       ++00000000000000000
** time_to_forget_tap : 00000000       000=----11111       11=---------32100000
** gesture_delay      : 00000000 ...   0000000000000 ...   00=----3210000000000
** drag_locked        : 00000000       0000000000000       00000000000000000000
** gesture            : 00000000       0001111111111       11000000001111100000
**
** Note:
**  0) There is no gesture if gesture_delay is non zero.
**  1) multiple_tap_delay (gesture_delay) should be less than tap_interval (time_to_forget_tap),
**     or else no second tap is reported.
**  2) Three fast taps (shorter than multiple_tap_delay) would reset gesture_delay the second time 
**     and only report 2 taps (the first and last). (likewise with more fast taps). BUG ?
**
**
** Drag lock mechanism: High when touched. (=: assigned, -: decreased, +: increased)
**                                       tap_interval
**                         ________________  v ___   ___        ___  ___
**                         |tap_l/u_limit |    |not tap|        | tap  |
**         -----------------              ------       ----  ----      ----- 
** 
** stroke_x/y         :    =                   =                =
** finger_on_pad_timer: 0000123+       +++00000123++  +++00  000123  +++00000
** time_to_forget_tap : 00000000       000=----11111  11100  000000  00000000
** gesture_delay      : 00000000 ...   0000000000000  00000  000000  00000000
** drag_locked        : 00000000       0000000000000  00111  111111  11100000
** gesture            : 00000000       0001111111111  11111  111111  11100000
**
** Note:
**  0) There is gesture as long as drag_lock is on
*/


static void tp_find_gestures (report_type *report) 
{
  static int time_to_forget_tap = 0;
  static int stroke_x;
  static int stroke_y;
  static int drag_locked = 0;


  if (report->fingers > 0) {
	    
    /* finger down for the first time */
    if (finger_on_pad_timer == 0) { 
      stroke_x = report->x;
      stroke_y = report->y;
    }
    
    /* don't want timer to overflow */
    if (finger_on_pad_timer < tap_upper_limit_packet) 
      finger_on_pad_timer ++; 
    
    /* dragging and consecutive tap gestures is to end with finger up 
     *  forget fast that there was a tap if this is not a part of a tap.*/
    if (time_to_forget_tap > 0) 
      time_to_forget_tap = 1;
    
  } else { /* interesting things happen when finger is up */
      
    /* tap determination: Was the finger long enough on the pad and not too
     * long, while staying at the same place.
     */
    if ((finger_on_pad_timer > tap_lower_limit_packet) &&  /* minimum finger down time */
	(finger_on_pad_timer < tap_upper_limit_packet) &&  /* maximum finger down time */
	((distance((double)(stroke_x - last_report.x),  /* maximum range for finger to drift while down */
		   (double)(stroke_y - last_report.y))
	 < sqr((double)tap_range)) ||
	 (multi_finger_tap_enabled && was_fingers > 1))) {
      
      /* not a consecutive tap? */
      if (time_to_forget_tap == 0) 
	gesture_delay = 0; /* right -> don't delay gesture */
      else { /* a consecutive tap! */
	gesture_delay = multiple_tap_delay * 80 / 1000; /* delay gesture to create multiple click */
      }

      /* Setup gesture time to count down */
      time_to_forget_tap = tap_interval * 80 / 1000; 

      /* Save the number of fingers used to do the tap */
      was_tap_fingers = was_fingers; 
      
      /* is drag locked, ifso unlock it and don't gesture. */
      if (drag_locked) {
	drag_locked = 0; 
	time_to_forget_tap = 0;
	gesture_delay = 0; 
	was_fingers=0;
	was_tap_fingers = 0;
      }
      
    } else { /* It was not a tap */
      
      /* a drag to lock?  If user did a tap and quickly hold the finger longer than a tap.
       */
      if (drag_lock_enabled && !drag_locked && 
	  (time_to_forget_tap > 0) && (finger_on_pad_timer >= tap_upper_limit_packet)) {
	drag_locked = 1;
	was_tap_fingers = was_fingers; 
	time_to_forget_tap = 0;
	gesture_delay = 0; 
      }
      
      if (time_to_forget_tap <= 1) {
	gesture_delay = 0; 
	was_fingers=0;
	was_tap_fingers = 0;
      }
      if (time_to_forget_tap  > 0) time_to_forget_tap --;
      if (gesture_delay > 0) gesture_delay --;
      
    }
    
#   ifdef DEBUG_TAPS
    if (finger_on_pad_timer)
      gpm_report (GPM_PR_DEBUG,"A tap? %d < %d < %d && (%d)^2 + (%d)^2 = %d < %d",
		  tap_lower_limit_packet,finger_on_pad_timer,tap_upper_limit_packet,
		  stroke_x-last_report.x,stroke_y-last_report.y,
		  sqr(stroke_x-last_report.x)+sqr(stroke_y-last_report.y),tap_range*tap_range);
#   endif

    finger_on_pad_timer = 0;
    
  }
  
  report->gesture  = ((time_to_forget_tap > 0) && (gesture_delay == 0)) || drag_locked;
}


/*
** tp_process_report
**
** Process the touchpad report. Do tossing mechanism, edge mechanism (edge
** extension, corner taps), speed pressure.
**
** Tossing mechanism: High when touched.  (=: assigned, -: decreased)
**                                                                         (Pause)
**                 ________________________        _____         ______ prevent_toss ___
**                 | min/max_toss_packets |  < 1s  |      ....        |      ...     |  
** -----------------                      ----------                  -----       ----  
** 
** is_tossing : 000000000000000000000000001111111111000000000000000000000000000000000000 
** was_tossing: 000000000000000000000000000000000001111111111111111111111111111111100000
** toss_timer :    =======================         ====================---- ... 321000==  
**                           
** Note 
** 0) When is_tossing is high a motion is reported/calculated.
** 1) if prevent_toss delay is 0 then toss to toss is allowed without pause.
** 2) if the pause is to short (< prevent_toss), the timers start over!
** 3) The 1 second limit is due to the touchpad only sends packets one second after a release.
** 
**
*/
static void tp_process_report (Gpm_Event *state,
			       report_type *report) 
{
  location_type loc,tmp_motion;
  int           edges;
  float         tmp_speed_factor,tmp_edge_speed;
  float         edge_pressure_speed_factor,pressure_speed_factor;
  int           edge_motion_on;

  /* extract location and edges */
  loc.x = report->x;
  loc.y = report->y;
  edges = tp_edges (loc);
  last_locs [mod4 (packet_num)] = loc;

  if (report->fingers > 0) {

    if (tossing_enabled) {
      /* this is the cue to stop tossing, if we are tossing so
         Force a touch free pause before a new tossing is allowed (prevent toss time) */
      was_tossing = (was_tossing || is_tossing);
      toss_timer  = prevent_toss_packets;
      is_tossing  = 0;
      
      /* if we start tossing then this is from where */
      if (last_report.fingers == 0) {
	touch_loc = loc;
      }
    }

    /* Save the edge state, so we can detect a movement from non edge to edge. */
    if (!edges)
      was_non_edge = 1;

    was_kept_edges &= edges;

    /* Need enough packets to perform smoothing on dy and dx
    *  but it should work with only 2, so why is 4 required? */
    if (packet_num > 3) {

      /* Is this start of scrolling? */
      if ((scrolling_enabled || hscrolling_enabled) && !is_scrolling) {
	is_scrolling  = (((scrolling_enabled  && was_kept_edges & scrolling_edge)   || /* Note: only one &! */
                          (hscrolling_enabled && was_kept_edges & hscrolling_edge)) && 
			 !last_report.gesture      && /* No consecutive tap */
			 (!is_corner(edges)        || /* Not a corner or */
			  !corner_taps_enabled     || /* corners disabled or */
			  is_corner_unused(edges)));  /* no corner action */

	/* If scrolling is found now, then set which way and inhibits taps by
	** setting the finger_on_pad_timer higher than are allowed for a
	** tap. Only alow to start one type of scrolling at a time. */
	if (is_scrolling) {
	  is_vscrolling =                    scrolling_enabled && (was_kept_edges & scrolling_edge);
	  is_hscrolling = !is_vscrolling && hscrolling_enabled && (was_kept_edges & hscrolling_edge);

	  if (scrolling_inhibits_taps)
	    finger_on_pad_timer = tap_upper_limit_packet;
	}
      }

      /* 
      ** 1) if edge motion is enabled, only activate if we moved into the edge
      ** or if not using the corners taps.  Don't activate right away after a
      ** gesture from a corner tap.  2) No harm is done if activated when
      ** scrolling (as long as the scrolling edges are set to normal
      ** directions). However dont start if finger is placed at the corner. 3)
      ** If user is tap holding (then he cannot lift the finger and hit the
      ** touchpad boarder).
      */

      edge_motion_on  = (edges && edge_motion_enabled && was_non_edge);
      edge_motion_on |= (edges && auto_scrolling_enabled && is_scrolling && !is_corner(was_kept_edges));
      edge_motion_on |= (edges && tap_hold_edge_motion_enabled && last_report.gesture && 
			 was_non_edge);


      /* compute the speed factor based on pressure */
      edge_pressure_speed_factor = standard_speed_factor;
      pressure_speed_factor      = standard_speed_factor;
      tmp_speed_factor           = standard_speed_factor;

      if (report->pressure > speedup_pressure) {
	tmp_speed_factor *= 1.0 + ((report->pressure - speedup_pressure) *
				   speedup_pressure_factor);
      }

      /* use the edge speed factor if edge_motion_speed is enabled */
      if (pressure_speed_enabled)
	pressure_speed_factor = tmp_speed_factor;

      if (edge_pressure_speed_enabled)
	edge_pressure_speed_factor = tmp_speed_factor;

      if (auto_scrolling_enabled && is_scrolling)
	edge_pressure_speed_factor *= 1.0 + auto_scrolling_factor; 

      /* Calculate dx and dy depending on whether we are at an edge */

      tmp_edge_speed = default_edge_speed * edge_pressure_speed_factor;
      tmp_motion     = tp_calc_motion(pressure_speed_factor);

      if (edge_motion_on && (edges & TOP_EDGE))
	 state->dy = -tmp_edge_speed;
      else if (edge_motion_on && (edges & BOTTOM_EDGE))
	 state->dy =  tmp_edge_speed;
      else
	 state->dy = -tmp_motion.y;
      
      if (edge_motion_on && (edges & LEFT_EDGE))
	 state->dx = -tmp_edge_speed;
      else if (edge_motion_on && (edges & RIGHT_EDGE))
	 state->dx =  tmp_edge_speed;
      else
	 state->dx =  tmp_motion.x;

    } /* If (packet_num > 3) */

    was_tap_edges = edges;
    packet_num++;
  } else {
    /* No finger on the pad */
    /* Start the tossing action if enabled */
    if (tossing_enabled && was_fingers == 1 &&
	!was_tossing    &&
	!is_scrolling   &&
	(packet_num > min_toss_packets) &&
	(packet_num < max_toss_packets)) {
      float dx, dy;

      dx =    last_locs [mod4 (packet_num - 1)].x - touch_loc.x;
      dy = - (last_locs [mod4 (packet_num - 1)].y - touch_loc.y);

      if (distance (dx, dy) > min_toss_dist__2) {
	is_tossing = 1;

	/* determine the toss speed */
	if (does_toss_use_static_speed) {
	  toss_speed.x = (standard_speed_factor * static_toss_speed * dx /
			  (abs (dx) + abs (dy)));
	  toss_speed.y = (int)(standard_speed_factor * static_toss_speed * dy /
			       (abs (dx) + abs (dy)));
	} else {
	  toss_speed.x = dx * standard_speed_factor * toss_speed_factor;
	  toss_speed.y = dy * standard_speed_factor * toss_speed_factor;
	}

#if DEBUG_TOSS
	gpm_report (GPM_PR_DEBUG,"dx: %2.1f  dy: %2.1f  tdist^2: %2.1f",
		    dx, dy, distance (dx, dy));
	gpm_report (GPM_PR_DEBUG,"tossx: %d  tossy: %d", toss_speed.x, toss_speed.y);
#endif
      }
    }

    /* no fingers therefore: no edge, and restart packet count */
    was_non_edge = 0;
    was_kept_edges = ALL_EDGES;
    is_scrolling = 0;
    scrolling_speed_timer = 0;
    packet_num = 0;
  }


  /* if we are tossing then apply the toss speed. Do this before anything else
  ** as we then can toss scroll etc */
  if (tossing_enabled && is_tossing) {
    state->dx = toss_speed.x;
    state->dy = toss_speed.y;
  }


  /* if we are scrolling then stop moving and report wheel amount.  The reason
  ** for having this above buttons actions is buttons can then move the
  ** mouse while scrolling. 
  */
  if (is_scrolling || is_always_scrolling) {
    if (is_vscrolling || is_always_scrolling) {
      if (scrolling_edge & (LEFT_EDGE | RIGHT_EDGE))
	vscrolling_amount_left -= state->dy;
      else
	vscrolling_amount_left -= state->dx;
    }
    if (is_hscrolling || is_always_scrolling) {
      if (hscrolling_edge & (TOP_EDGE | BOTTOM_EDGE))
	hscrolling_amount_left -= state->dx;
      else
	hscrolling_amount_left -= state->dy;
    }
    state->dx = 0;
    state->dy = 0;
  }

  /* check for (corner)buttons if we didn't just complete a toss or is scrolling */
  if (!is_tossing && !was_tossing && !is_scrolling) {
    /*
    ** If there is no gesture clear, but don't clear if we
    ** are about to make a double tap. 
    */
    if (report->gesture) {
      if(tap_gesture_enabled) tp_process_finger_taps (state, report);
      if(corner_taps_enabled) tp_process_corner_taps (state, report);
    } else if (!gesture_delay && !fake_time_to_forget_tap) {
      last_corner_action = GPM_B_NOT_SET;
      last_finger_action = GPM_B_NOT_SET;
    }
  }

  /* Handle buttons */
  tp_process_button_press (state, report);

  /* Is there any amount of scrolling left?  Should be checked after corner actions. */
  tp_handle_scrolling(state);


  was_tossing  = was_tossing && toss_timer;
  if (was_tossing)
    toss_timer--;

  /* remember the last state of the finger for toss processing */
  last_report   = *report;
  last_state    = *state;

  /* Don't do anything if the buttons or motion pad is not enabled, but after
   * corner actions are done so it can be turn on again. */
  if (!touchpad_motion_enabled) {
    state->dx      = 0;
    state->dy      = 0;
  }

  /* Extra_motion is used to add motion if the buttons is used to move the
   * mouse, or if the touchpads was incapable of detecting multifingers in time
   */
  state->dx += extra_motion.x;
  state->dy += extra_motion.y;
  extra_motion.x = 0; 
  extra_motion.y = 0;

  if (!touchpad_enabled) {
    state->buttons = GPM_B_NONE;
    state->dx      = 0;
    state->dy      = 0;
  }
  
#if DEBUG_REPORTS
  tp_dump_report_data (*report, edges, state);
#endif

}


/*
** syn_read_config_file
**
** Read the configuration data from the global config file
** SYSCONFDIR "/gpm-syn.conf".
*/
static void tp_read_config_file (char* config_filename) 
{
  char line [80];
  char *token;
  char *end_ptr;
  int  param, tmp_read_int_param;
  FILE *config;
  char full_filename[100];
  int  status;
  float tmp_read_float_param;

  status = snprintf(full_filename,100,SYSCONFDIR "/%s",config_filename);
  if (status < 0) {
    gpm_report (GPM_PR_WARN,"Too long path for configure file: %s", config_filename); 
    return;
  }


  if ( !(config = fopen (full_filename, "r")) ) {
    gpm_report (GPM_PR_WARN,"Failed to open configfile: %s", full_filename);    
  }else{
    while (fgets (line, 80, config)) {
      if (line [0] == '[') {
	if ( (token = strtok (line, "[] \t")) ) {
	  param = 0;

	  /* which param is it */
	  while (param_data [param].name &&
		 strcasecmp (token, param_data [param].name) != 0) {
	    param++;
	  }

	  /* was a param found? */
	  if (!param_data [param].name) {
	    gpm_report (GPM_PR_WARN,"Unknown parameter %s", token);
	  } else {
	    token = strtok (NULL, "[] \t");

	    switch (param_data [param].p_type) {
	    case Integer_Param:
	      tmp_read_int_param = strtol (token, &end_ptr, 0);
	      if (end_ptr != token)
		*(param_data [param].addr.int_p) = tmp_read_int_param;
	      else
		gpm_report (GPM_PR_WARN,"Integer value (%s) for parameter %s is invalid",
			    token, param_data [param].name);
#             if DEBUG_PARAMS
	      gpm_report (GPM_PR_INFO,"Param %s set to %d",
			  param_data [param].name,
			  *(param_data [param].addr.int_p));
#             endif
	      break;

	    case Float_Param:
	      tmp_read_float_param = strtod (token, &end_ptr);
	      if (end_ptr != token)
		*(param_data [param].addr.float_p) = tmp_read_float_param;
	      else
		gpm_report (GPM_PR_WARN,"Float value (%s) for parameter %s is invalid",
			    token, param_data [param].name);
#             if DEBUG_PARAMS
	      gpm_report (GPM_PR_INFO,"Param %s set to %3.3f",
			  param_data [param].name,
			  *(param_data [param].addr.float_p));
#             endif
	      break;

	    case Flag_Param:
	      if (index ("YyTt1", token [0])) {
		*(param_data [param].addr.flag_p) = 1;
	      } else if (index ("NnFf0", token [0])) {
		*(param_data [param].addr.flag_p) = 0;
	      } else {
		gpm_report (GPM_PR_WARN,"Flag value (%s) for parameter %s is invalid",
			    token, param_data [param].name);
	      }
#             if DEBUG_PARAMS
	      gpm_report (GPM_PR_INFO,"Param %s set to %s",
			  param_data [param].name,
			  (*(param_data [param].addr.flag_p) ?
			   "True" : "False"));
#             endif
	      break;

	    case Action_Param:
	      tmp_read_int_param = strtol (token, &end_ptr, 0);
	      if (end_ptr != token)
		param_data [param].addr.corner_p->action = tmp_read_int_param;
	      else
		gpm_report (GPM_PR_WARN,"Action value (%s) for parameter %s is invalid",
			    token, param_data [param].name);
#             if DEBUG_PARAMS
	      gpm_report (GPM_PR_INFO,"Param %s set to %d",
			  param_data [param].name,
			  param_data [param].addr.corner_p->action);
#             endif
	      break;

	    default:
	      gpm_report (GPM_PR_WARN,"Internal error: Parameter type %d is unknown",param_data [param].p_type);
	    }
	  }
	}
      }
    }

    fclose (config);
  }
}


/*
** syn_process_touchpad_config
**
** Extract important information and report (as desired) to the user.
*/
static void syn_process_config (tp_class *tp) 
{
  identify_type identify     = tp->identify[DEVICE_TOUCHPAD];
  model_id_type model        = tp->model[DEVICE_TOUCHPAD];
  ext_cap_type  capabilities = tp->capabilities[DEVICE_TOUCHPAD];   
  sensor_info_type sensor    = tp->sensor[DEVICE_TOUCHPAD];

  gpm_report (GPM_PR_INFO, "     Firmware version %d.%d\n",
	      identify.major, identify.minor);

  tp_read_config_file ("gpm-syn.conf");

  
  /* Limit the options depending on the touchpad capabilities. This should be
     done after reading the configure file so they may be turned off on purpose
     and can't be turned on if not supported. */
  if(!capabilities.extended){
    wmode_enabled         = 0;
  }

  if(!wmode_enabled || !capabilities.palm_detect){
    palm_detect_enabled   = 0;
  }

  if(!wmode_enabled || !capabilities.stick){
    stick_enabled          = 0;
    stick_pressure_enabled = 0;
  }
  
  /* fake_forget_tap_interval must be set if the hardware does the gesture. */
  if(!wmode_enabled)
    fake_forget_tap_interval = 1;

  /* Save important information. Reverse if the touchpad is portrait */
  if (model.portrait) {
    x_per_mm = sensor.y_per_mm;
    y_per_mm = sensor.x_per_mm;
    res_x = (int) (x_per_mm * sensor.height_mm);
    res_y = (int) (y_per_mm * sensor.width_mm);
  } else {
    x_per_mm = sensor.x_per_mm;
    y_per_mm = sensor.y_per_mm;
    res_x = (int) (x_per_mm * sensor.width_mm);
    res_y = (int) (y_per_mm * sensor.height_mm);
  }

  /* convert the tap times to packets (80 pkts/sec and 1000 ms/sec) */
  tap_lower_limit_packet = tap_lower_limit * 80 / 1000;
  tap_upper_limit_packet = tap_upper_limit * 80 / 1000;

  /* Convert the toss dist to touchpad resolution from mm */
  min_toss_dist__2 = sqr (min_toss_dist * (x_per_mm + y_per_mm) / 2);

  /* convert the toss times to packets (80 pkts/sec and 1000 ms/sec) */
  max_toss_packets     = max_toss_time * 80 / 1000;
  min_toss_packets     = min_toss_time * 80 / 1000;
  prevent_toss_packets = prevent_toss_time * 80 / 1000;

}


/****************************************************************************
**
** ROUTINES for interfacing to a SERIAL touchpad
**
****************************************************************************/




static unsigned char tp_hextoint (unsigned char byte1,
				  unsigned char byte2) 
{
  unsigned char bytes [3];
  int result;

  bytes [0] = byte1;
  bytes [1] = byte2;
  bytes [2] = '\0';
  sscanf (bytes, "%x", &result);
  return result;
}

static void tp_serial_flush_input (int fd) 
{
  struct timeval tv;
  fd_set rfds;
  unsigned char junk;
  
  FD_ZERO(&rfds);
  FD_SET (fd, &rfds);
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  usleep (50000);

  while (select (fd+1, &rfds, NULL, NULL, &tv) == 1) {
#if DEBUG_FLUSH
    gpm_report (GPM_PR_DEBUG,"Serial flushing");
    fflush (stdout);
#endif
    read (fd, &junk, 1);
#if DEBUG_FLUSH
    gpm_report (GPM_PR_DEBUG," %c", junk);
#endif
    FD_ZERO(&rfds);
    FD_SET (fd, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 0;
  }
}

static void tp_serial_read (int fd,
			    unsigned char *bytes,
			    size_t count) 
{
  struct timeval tv;
  fd_set rfds;
  int num_read = 0;
  int read_count;

  FD_ZERO (&rfds);
  FD_SET (fd, &rfds);
  tv.tv_sec = 1;
  tv.tv_usec = 0;

  while ((select (fd+1, &rfds, NULL, NULL, &tv) == 1) &&
	 (num_read < count)) {
    read_count = read (fd, &bytes [num_read], count - num_read);
    num_read += read_count;

    FD_ZERO (&rfds);
    FD_SET (fd, &rfds);
  }

  for (; num_read < count; num_read++) {
     bytes [num_read] = '\0';
  }
}

/* Write a string of commands */
static void tp_serial_send_cmd(int fd,
			       unsigned char *cmd) 
{
  unsigned char junk [15];

  tp_serial_flush_input (fd);
  write (fd, cmd, strlen (cmd));
  tp_serial_read (fd, junk, strlen (cmd));
#if DEBUG_FLUSH
  junk [strlen (cmd)] = '\0';
  gpm_report (GPM_PR_DEBUG,"serial tossing: %s", junk);
#endif
}

/* write 'mode' to a serial touchpad, STIG 58 */
static void syn_serial_set_mode (int fd,
				 unsigned char mode) 
{
  unsigned char bytes [15];

  sprintf (bytes, "%%C3B%02X5555", mode);
#if DEBUG_SENT_DATA
  gpm_report (GPM_PR_DEBUG,"modes: %s", bytes);
#endif
  tp_serial_send_cmd (fd, bytes);
}

/* read the identification from the serial touchpad, STIG 57*/
static void syn_serial_read_ident (tp_class *tp) 
{
  identify_type *identify = &(tp->identify[DEVICE_TOUCHPAD]);
  unsigned char bytes [5];
  int fd = tp->fd;

  tp_serial_send_cmd (fd, "%A");
  tp_serial_read (fd, bytes, 4);

#if DEBUG_SENT_DATA
  bytes [4] = '\0';
  gpm_report (GPM_PR_DEBUG,"Serial ident: %s", bytes);
#endif

  if ( bytes [0] == 'S' && bytes [0] == 'T'){
    /* reformat the data
     * The out commented is wrong according to STIG page 57.*/
    /* info->model_code = (bytes [2] & 0x07) >> 3; */
    identify->model_code = bytes [2] >> 3;
    identify->major      = (bytes [2] & 0x07);
    identify->minor      = bytes [3];
  }else{
    gpm_report (GPM_PR_ERR,"PS/2 serial device doesn't appear to be a synaptics touchpad\n");
    identify->model_code = 0;
    identify->minor      = 0;
    identify->major      = 0;
  }
}



/* read the model_id from the serial touchpad (in ps/2 format) */
static void syn_serial_read_model_id (tp_class *tp) 
{
  int fd = tp->fd;
  identify_type identify = tp->identify[DEVICE_TOUCHPAD];
  unsigned char bytes [7];
  int model_int;

  /* 
   * for older touchpads this command is not supported and no response will
   * come.  We should do non blocking input here to handle that case 
   * and return byte2 as 0x47 ... later.
   *
   * pebl: It is easier just to check version number less than 3.2, STIG page 60.
   */
  if ( (identify.major >= 4) ||
       (identify.major == 3 && identify.minor >= 2)){
    tp_serial_send_cmd (fd, "%D");
    tp_serial_read (fd, bytes, 6);

    /* reformat the data */
    model_int = ((tp_hextoint (bytes [0], bytes [1]) << 16) |
		 (tp_hextoint (bytes [2], bytes [3]) << 8) |
		 (tp_hextoint (bytes [4], bytes [5])));
    
#   if DEBUG_SENT_DATA
      bytes [6] = '\0';
      gpm_report (GPM_PR_DEBUG,"Serial model id: %s", bytes);
#   endif

  }else{
    model_int = 0;
  }

  syn_extract_model_id_info (&(tp->model[DEVICE_TOUCHPAD]),model_int);
}


/* read the mode bytes and capabilities from the serial touchpad, STIG 57 */
static void syn_serial_read_cap (tp_class *tp) 
{
  int fd = tp->fd;
  identify_type identify = tp->identify[DEVICE_TOUCHPAD];
  unsigned char bytes [8];
  int cap_int = 0;

  tp_serial_send_cmd (fd, "%B");
  tp_serial_read (fd, bytes, 8);

#if DEBUG_SENT_DATA
  bytes [7] = '\0';
  gpm_report (GPM_PR_DEBUG,"Serial capabilities: %s", bytes);
#endif

  if (identify.major >= 4){
    if (bytes [0] == '3' && bytes [0] == 'B'){
      cap_int = ((tp_hextoint (bytes [4], bytes [5]) << 8) |
		 (tp_hextoint (bytes [6], bytes [7])));
    }else{
      gpm_report (GPM_PR_ERR,"PS/2 serial device doesn't appear to be a synaptics touchpad\n");
    }    
  }

  syn_extract_extended_capabilities(&(tp->capabilities[DEVICE_TOUCHPAD]),cap_int);
}


static void syn_serial_absolute_mode (int fd)
{
  syn_serial_set_mode (fd, (ABSOLUTE_MODE    |
			    HIGH_REPORT_RATE |
			    USE_9600_BAUD    |
			    (TP->model[DEVICE_TOUCHPAD].new_abs ? EXTENDED_REPORT : NORMAL_REPORT) |
			    (wmode_enabled ? REPORT_W_ON : REPORT_W_OFF)));
}


/*------------------------------------------------------------------------*/
/*   PS/2 Utility functions.                                              */
/*      Adapted from tpconfig.c by C. Scott Ananian                       */
/*------------------------------------------------------------------------*/

/* PS2 Synaptics is using LSB, STIG page 29. 
**
** After power on or reset the touchpads are set to these defaults:
** 100 samples per second
** Resolution is 4 counts per mm
** Scaling 1:1
** Stream mode is selected
** Data reporting is disabled
** Absolute mode is disabled
*/


/* Normal ps2 commands, Command set is on STIG page 33  */
#define PS2_RESET         0xFF      /* Reset */
#define PS2_RESEND        0xFE      /* Resend command */
#define PS2_SET_DEFAULT   0xF6      /* Set default */
#define PS2_DISABLE_DATA  0xF5      /* Stop sending motion data packets */
#define PS2_ENABLE_DATA   0xF4      /* Start sending motion data packets */
#define PS2_SAMPLE_RATE   0xF3      /* Set sample rate to value in a following byte */
#define PS2_READ_DEVICE   0xF2      /* Read device type (ack and idcode) */
#define PS2_REMOTE_MODE   0xF0      /* Set touchpad in remote mode */
#define PS2_WRAP_MODE     0xEE      /* Enter wrap mode */
#define PS2_END_WRAP_MODE 0xEC      /* Leave wrap mode */
#define PS2_READ_DATA     0xEB      /* Read move data (in remote mode)*/
#define PS2_STREAM_MODE   0xEA      /* Enter stream mode (device sends move data) */
#define PS2_STATUS_REQ    0xE9      /* Ask for status request */
#define PS2_RESOLUTION    0xE8      /* Set resolution to next transmitted byte */
#define PS2_SCALE_12      0xE7      /* Set scale to 1:2 */
#define PS2_SCALE_11      0xE6      /* Set scale to 1:1 */


/* Normal ps2 response */
#define PS2_ERROR         0xFC      /* Error, after a reset,resend or disconnect*/
#define PS2_ACK           0xFA      /* Command acknowledge */
#define PS2_READY         0xAA      /* Send after a calibration or ERROR */
#define PS2_MOUSE_IDCODE  0x00      /* Identification code (meaning mouse) sent after a PS2_READY */


/* Additional synaptic commands*/
#define PS2_SYN_CMD       0xE8      /* Four of these each with an following byte encodes a command*/
#define PS2_SYN_INERT     0xE6      /* This ps2 command is ignored by synaptics */
#define PS2_SYN_SET_MODE1 0x0A      /* Set the mode byte 1 instead of sample rate (used after a sample rate cmd) */
#define PS2_SYN_SET_MODE2 0x14      /* Set the mode byte 2 instead of sample rate (used after a sample rate cmd).
				     * All other sample rate gives undefined behavior (used to address 4 byte mode)*/
#define PS2_SYN_CMD_STICK 0x28      /* Send byte to stick */
#define PS2_SYN_STATUS_OK 0x47      /* Special synaptics Status report is recognized */


/* These are the commands that can be given (encoded) by PS_SYN_CMD */
#define PS2_SYN_CMD_IDENTIFY     0x00 /* Identify Touchpad */
#define PS2_SYN_CMD_MODES        0x01 /* Read Touchpad Modes */
#define PS2_SYN_CMD_CAPABILITIES 0x02 /* Read capabilities */
#define PS2_SYN_CMD_MODEL_ID     0x03 /* Read model id */
#define PS2_SYN_CMD_SERIAL_NO_P  0x06 /* Read serial number prefix */
#define PS2_SYN_CMD_SERIAL_NO_S  0x07 /* Read serial number suffix */
#define PS2_SYN_CMD_RESOLUTIONS  0x08 /* Read resolutions */
#define PS2_SYN_CMD_EXT_CAPABIL  0x09 /* Read extended capabilities */



/* read a byte from the ps/2 port */
static byte tp_ps2_getbyte(int fd) 
{
  byte b;
  ssize_t status;

# ifdef DEBUG_GETBYTE
  gpm_report(GPM_PR_DEBUG,"Getting byte");
# endif

  status = read(fd, &b, 1);
  if (status < 1) gpm_report(GPM_PR_ERR,"Getbyte failed with errno %d",errno);
    
# ifdef DEBUG_GETBYTE
  gpm_report(GPM_PR_DEBUG,"Got %X",b);
# endif

  return b;
}


/* write a byte to the ps/2 port, handling resend.*/
static byte tp_ps2_putbyte(int fd,
			   byte b) 
{
  byte ack;
  ssize_t status;

# ifdef DEBUG_PUTBYTE
  gpm_report(GPM_PR_DEBUG,"Send real byte %X",b);
# endif

  status = write(fd, &b, 1);
  if (status < 1) gpm_report(GPM_PR_ERR,"putbyte writing failed with errno %d",errno);
  status = read(fd, &ack, 1);
  if (status < 1) gpm_report(GPM_PR_ERR,"putbyte reading failed with errno %d",errno);

  if (ack == PS2_RESEND) {
    status = write(fd, &b, 1);
    if (status < 1) gpm_report(GPM_PR_ERR,"putbyte 2th writing failed with errno %d",errno);
    status = read(fd, &ack, 1);
    if (status < 1) gpm_report(GPM_PR_ERR,"putbyte 2th reading failed with errno %d",errno);
  }

# ifdef DEBUG_PUTBYTE_ACK
  gpm_report(GPM_PR_DEBUG,"Response %X to byte %X",ack,b);
# endif

  return ack;
}


/* Read a byte from the touchpad or use the Synaptics extended ps/2 syntax to
 * read a byte from the stick device. The variable stick is used to indicate
 * whether it is the touchpad or stick device that is meant.
 */
static byte syn_ps2_getbyte(int fd, int stick) 
{
  byte response[6];

  if (!stick) {
    response[1]=tp_ps2_getbyte(fd);
  } else {
    response[0]=tp_ps2_getbyte(fd);
    response[1]=tp_ps2_getbyte(fd);
    response[2]=tp_ps2_getbyte(fd);
    response[3]=tp_ps2_getbyte(fd);
    response[4]=tp_ps2_getbyte(fd);
    response[5]=tp_ps2_getbyte(fd);
    
    /* Do some sanity checking */
    if((response[0] & 0xFC) != 0x84) {
      gpm_report (GPM_PR_ERR,"Byte 0 of stick device response is not valid");
      return -1;
    }
    if((response[3] & 0xCC) != 0xC4) {
      gpm_report (GPM_PR_ERR,"Byte 3 of stick device response is not valid");
      return -1;
    }
  }

  return response[1];
}


/* write byte to the touchpad or use the Synaptics extended ps/2 syntax to write
 * a byte to the stick device. The variable stick is used to indicate
 * whether it is the touchpad or stick device that is meant.  */
static byte syn_ps2_putbyte(int fd,
			    int stick,
			    byte b) 
{
  byte ack;

# ifdef DEBUG_PUTBYTE
  gpm_report(GPM_PR_DEBUG,"Send byte %X to %s",b,(stick?"Stick":"Touchpad"));
# endif

  if (!stick) {
    ack = tp_ps2_putbyte(fd,b);
  } else {
    ack = syn_ps2_send_cmd(fd, DEVICE_TOUCHPAD, b);
    if (ack != PS2_ACK) gpm_report (GPM_PR_ERR,"Invalid ACK while tunneling a command");
    ack = tp_ps2_putbyte(fd, PS2_SAMPLE_RATE);
    if (ack != PS2_ACK) gpm_report (GPM_PR_ERR,"Invalid ACK to stick putbytet sample rate");
    ack = tp_ps2_putbyte(fd, PS2_SYN_CMD_STICK);
    if (ack != PS2_ACK) gpm_report (GPM_PR_ERR,"Invalid ACK to stick putbytet cmd stick");
    ack = syn_ps2_getbyte(fd,DEVICE_STICK);
  }

  if (ack != PS2_ACK)
    gpm_report (GPM_PR_ERR,"Invalid ACK to synps2 %s putbyte %02X, got %02X",
		(stick?"Stick":"Touchpad"),b,ack);

  return ack;
}


/* use the Synaptics extended ps/2 syntax to write a special command byte 
* STIG page 36: Send exactly four PS2_SYN_CMD (which is otherwise ignored)
* and after each a byte with the 2 first bits being the command (LSB). End it with
* either  PS2_SAMPLE_RATE or PS2_STATUS_REQ. It is hinted to send an inert command
* first so not having five or more PS2_SYN_CMD by coincident.
*
* If data is for the stick device every byte has to be encode by the above method.
*/
static byte syn_ps2_send_cmd(int fd,
			     int stick, 
			     byte cmd)
{
  int i;
  byte ack;

# ifdef DEBUG_CMD
  gpm_report(GPM_PR_DEBUG,"Send encapsulated byte %X to %s",cmd,(stick?"Stick":"Touchpad"));
# endif

  /* initialize with 'inert' command */
  ack = tp_ps2_putbyte(fd, PS2_SYN_INERT);
  for (i=0; i<4; i++) {
    ack = syn_ps2_putbyte(fd, stick, PS2_SYN_CMD);
    ack = syn_ps2_putbyte(fd, stick, (cmd>>6)&0x3);
    cmd<<=2;
  }

  return ack;
}

#if 0

/* write 'cmd' to mode byte 1.
 * This function is not used. 
 * Code 0x0A is unknown to me, maybe used in older synaptics?
 *
 * This is used for some old 2 byte control synaptics. The function is not
 * used, and is probably leftover from mixing with Van der Plas code.
 */
static void syn_ps2_set_mode1(int fd,
			      int stick, 
			      byte cmd)
{
  syn_ps2_send_cmd(fd, stick, cmd);
  tp_ps2_putbyte(fd, stick, PS2_SAMPLE_RATE);
  tp_ps2_putbyte(fd, stick, PS2_SYN_SET_MODE1);
}

#endif


/* write 'cmd' to mode byte 2 
 * See ps2_send_cmd. PS2_SR_SET_MODE stores the touchpad mode encoded in the 
 * four PS2_SYN_CMD commands
 */
static void syn_ps2_set_mode2(int fd,
			      int stick,
			      byte mode_flags)
{
  gpm_report (GPM_PR_DEBUG,"Set Synaptic PS/2 %s mode to %X", (stick?"stick":"touchpad"),mode_flags);

  syn_ps2_send_cmd(fd, stick, mode_flags);
  syn_ps2_putbyte (fd, stick, PS2_SAMPLE_RATE);
  syn_ps2_putbyte (fd, stick, PS2_SYN_SET_MODE2);
}


/* read three byte status ('a','b','c') corresponding to register 'cmd' 
*  Special status request for synaptics is given after a cmd.
*  Byte b is PS2_SYN_STATUS_OK to recognize a synaptics
*/
static void syn_ps2_status_rqst(int fd,
				int stick,
				byte cmd,
				byte *bytes)
{
  gpm_report (GPM_PR_DEBUG,"Status request for %s: %X", (stick?"stick":"touchpad"),cmd);

  syn_ps2_send_cmd(fd, stick, cmd);
  syn_ps2_putbyte (fd, stick, PS2_STATUS_REQ);
  bytes [0]=syn_ps2_getbyte(fd,stick);
  bytes [1]=syn_ps2_getbyte(fd,stick);
  bytes [2]=syn_ps2_getbyte(fd,stick);

  gpm_report (GPM_PR_DEBUG,"Status request answer: %X %X %X", bytes[0], bytes[1], bytes[2]);
}


#if 0

/* read the modes from the touchpad (in ps/2 format) */
static void syn_ps2_read_modes (int fd, int stick) 
{
  unsigned char bytes [3];

  syn_ps2_status_rqst (fd, stick, PS2_SYN_CMD_MODES, bytes);
# ifdef DEBUG
  gpm_report (GPM_PR_INFO,"Synaptic PS/2 %s modes: %02X", (stick?"stick":"touchpad"),bytes [2]);
# endif
}

#endif


/* read the identification from the ps2 touchpad */
static void syn_ps2_read_ident (tp_class *tp, int stick)
{
  identify_type *identify = &(tp->identify[stick]);
  byte bytes [3];
 
  syn_ps2_status_rqst (tp->fd, stick, PS2_SYN_CMD_IDENTIFY, bytes);
  if (bytes [1] != PS2_SYN_STATUS_OK) {
    gpm_report (GPM_PR_INFO,"PS/2 device doesn't appear to have synaptics %s identification\n",
		(stick?"sticks":"touchpads"));
    identify->minor      = 0;
    identify->model_code = 0;
    identify->major      = 0;

  } else {
    identify->minor      = bytes [0];
    identify->model_code = (bytes [2] >> 4) & 0x0f;
    identify->major      = bytes [2] & 0x0f;
  }
}


/* read the model_id from the ps2 touchpad/stick */
static void syn_ps2_read_model_id (tp_class *tp, int stick)
{
  model_id_type *model = &(tp->model[stick]);
  unsigned char bytes [3];
  int model_int;

  syn_ps2_status_rqst (tp->fd, stick, PS2_SYN_CMD_MODEL_ID, bytes);
  model_int = ((bytes [0] << 16) |
	       (bytes [1] << 8)  |
	       (bytes [2]));
  syn_extract_model_id_info (model, model_int);
}


/* read the extended capability from the ps2 touchpad, STIG page 15 */
static void syn_ps2_read_cap (tp_class *tp, int stick)
{
  ext_cap_type *cap = &(tp->capabilities[stick]);
  unsigned char bytes [3];
  int ext_cap_int;

  syn_ps2_status_rqst (tp->fd, stick, PS2_SYN_CMD_CAPABILITIES, bytes);

  if (bytes [1] != PS2_SYN_STATUS_OK) {
    gpm_report (GPM_PR_INFO,"PS/2 device doesn't appear to have synaptics %s capabilities\n",
		(stick?"stick":"touchpad"));
    ext_cap_int = 0;
  }else{
    ext_cap_int = bytes[0] << 8 | bytes[2];
  }

  syn_extract_extended_capabilities(cap, ext_cap_int);
}

/* Read the the extended requests from the ps2 touchpad, MBSS page 4 */
static void syn_ps2_read_ext_cap (tp_class *tp, int stick)
{
  int fd = tp->fd;
  ext_cap_type *cap = &(tp->capabilities[stick]);
  unsigned char bytes [3];
  int dummy_boardtype, dummy_product_id;
  
  switch (cap->ext_requests) {
  default:
    gpm_report (GPM_PR_WARN,"Synaptics PS/2 device has unknown extra requests, please report this\n"); 
  case 1:
    syn_ps2_status_rqst (fd, stick, PS2_SYN_CMD_EXT_CAPABIL, bytes);
    dummy_product_id =  bytes[0];
    dummy_boardtype  = (bytes[1] & 0x0C) >> 2;
    multi_buttons_no = (bytes[1] & 0xF0) >> 4;
  case 0:
    break; /* No extra capabilites */
  }

  if (multi_buttons_no > 8)
    gpm_report (GPM_PR_WARN,"Synaptics PS/2 device has unknown multi buttons supports, please report this\n"); 

  return; 
}

/*
 * Read device resolution, but only if device is capable.
 */
static void syn_ps2_read_resulution(tp_class *tp, int stick)
{
  int fd = tp->fd;
  resolution_type *resolution = &(tp->resolution[stick]);
  unsigned char bytes [3];

  if (tp->identify[stick].major >= 4) {
    syn_ps2_status_rqst (fd, stick, PS2_SYN_CMD_RESOLUTIONS, bytes);
    if ((bytes[1] & 0x80) && bytes[0] && bytes[2]) {
      resolution->Xupmm = bytes[0]; 
      resolution->Yupmm = bytes[2]; 
    }
  }
}


/*
 * tp_ps2_flush_data
 *
 * Flush any data in the queue and return the last read byte. But wait
 * the minimum time (50000 msec) a device has to answer with an ACK in.
 */

static int tp_ps2_flush_data (int fd)
{
  struct timeval tv;
  fd_set rfds;
  unsigned char status;
  int selstatus;

  gpm_report (GPM_PR_DEBUG,"Flushing data on PS/2 device");

  FD_ZERO(&rfds);
  FD_SET (fd, &rfds);
  tv.tv_sec = 0;
  tv.tv_usec = 50000;

  while ((selstatus = select (fd+1, &rfds, NULL, NULL, &tv)) > 0) {
    read (fd, &status, 1);
#   if DEBUG_RESET
    gpm_report (GPM_PR_DEBUG,"PS/2 device flush data: %02X", status);
#   endif
    FD_ZERO(&rfds);
    FD_SET (fd, &rfds);
    tv.tv_sec = 0;
    tv.tv_usec = 50000;
  }

  if (selstatus < 0)
    gpm_report (GPM_PR_WARN,"Select returned error %d", selstatus);
    
  return status;
}


/*
 * tp_ps2_disable_data
 *
 * Disable data reporting (streaming), and flush eventual old packets. As the
 * kernel keeps a queue of received data from the touchpad, the next byte we
 * read could be old data and not the ack to our command or request.  Disable
 * data should always be called before sending commands or request to the
 * touchpad.
 *
 * Note that this is a general ps2 command which should not be in this file,
 * but in mice.c.
 */
static void tp_ps2_disable_data (int fd) 
{
  byte cmd = PS2_DISABLE_DATA;
  unsigned char status;

  gpm_report (GPM_PR_DEBUG,"Disabling stream data on PS/2 device");
  
  write(fd,&cmd,1);
  status = tp_ps2_flush_data(fd);
  
  if (status != PS2_ACK)
    gpm_report (GPM_PR_ERR,"Invalid ACK to disable stream command, got %02X",status);
}


/* 
 * syn_ps2_enable_data
 *
 * Enable data after a disable data command. Should have called the disable data
 * before calling this function.
 */

static void syn_ps2_enable_data(int fd)
{  
  gpm_report (GPM_PR_DEBUG,"Enables data on ps2 devices.");

  if (stick_enabled)
    syn_ps2_putbyte(fd,DEVICE_STICK,PS2_ENABLE_DATA);
  syn_ps2_putbyte(fd,DEVICE_TOUCHPAD,PS2_ENABLE_DATA);
}


/* 
 * syn_ps2_send_reset
 *
 * Send reset command and absorb additional READY, IDCODE from the
 * touchpad. Should have called the disable data before calling this function.
 * Synaptics garanties always to return PS2_READY. STIG page 31 and 48.
 */

static void syn_ps2_send_reset(int fd, int stick)
{
  byte status,id_code = PS2_MOUSE_IDCODE;
  byte reset_cmd = PS2_RESET;

  gpm_report(GPM_PR_DEBUG,"Reseting Synaptic PS/2 %s\n",(stick?"Stick":"Touchpad"));

  /* Send reset command without eating the ack. */
  if(!stick) {
    write(fd,&reset_cmd,1);
    status = tp_ps2_getbyte(fd);
  } else {
    status = syn_ps2_putbyte(fd, stick, reset_cmd);
  }

  /* Sometimes the touchpad sends additional ready,idcode before ack the reset command.
   * I dont know why! */
  while (status == PS2_READY){
    gpm_report(GPM_PR_WARN,"PS/2 device sending additional READY, ID CODE.\n");
    id_code = syn_ps2_getbyte(fd,stick);
    status  = syn_ps2_getbyte(fd,stick);
  }

  if (status != PS2_ACK || id_code != PS2_MOUSE_IDCODE){
    gpm_report(GPM_PR_ERR,"Sending reset command to PS/2 Device failed: No ACK, got %02X.\n",status);
  }

  /* Wait 750 ms to recalibrate. */
  usleep(750000);

  if ( (status  = syn_ps2_getbyte(fd,stick)) != PS2_READY ){
    gpm_report(GPM_PR_ERR,"Reseting PS/2 Device failed: No READY, got %02X.\n"
	                  "Check pc_keyb.c for reconnect smartness.\n",status);
  }
  if ( (id_code = syn_ps2_getbyte(fd,stick)) != PS2_MOUSE_IDCODE){
    gpm_report(GPM_PR_ERR,"Reseting PS/2 Device failed: Wrong ID, got %02X.\n",id_code);
  }

}


static int syn_ps2_find_best_mode(int fd, int stick)
{
  byte init_exps2[] = { PS2_SAMPLE_RATE, 200, PS2_SAMPLE_RATE, 200, PS2_SAMPLE_RATE, 100, PS2_READ_DEVICE,};
  byte init_imps2[] = { PS2_SAMPLE_RATE, 200, PS2_SAMPLE_RATE, 100, PS2_SAMPLE_RATE, 80 , PS2_READ_DEVICE,};
  int counter;
  byte read_id;

  gpm_report(GPM_PR_DEBUG,"Detecting Synaptic PS/2 %s's best intellimouse mode",(stick?"Stick":"Touchpad"));    

  for (counter = 0; counter < sizeof(init_exps2); counter++) {
    syn_ps2_putbyte(fd,stick,init_exps2[counter]);
  }
  if ((read_id = syn_ps2_getbyte(fd,stick)) == 4) {
    gpm_report(GPM_PR_DEBUG,"Synaptic PS/2 %s can do exps2\n",(stick?"Stick":"Touchpad"));    
    return 4;
  }

  for (counter = 0; counter < sizeof(init_imps2); counter++) {
    syn_ps2_putbyte(fd,stick,init_imps2[counter]);
  }
  if ((read_id = syn_ps2_getbyte(fd,stick)) == 3) {
    gpm_report(GPM_PR_DEBUG,"Synaptic PS/2 %s can do imps2\n",(stick?"Stick":"Touchpad"));    
    return 3;
  }

  gpm_report(GPM_PR_DEBUG,"Synaptic PS/2 %s can do no intellimouse mode\n",(stick?"Stick":"Touchpad"));    
  return 0;
}


/*
** syn_ps2_absolute_mode
**
** Put the touchpad into absolute mode.
*/

static void syn_ps2_absolute_mode(int fd)
{

  /* select 6 byte packet, high packet rate, no-sleep */
  syn_ps2_set_mode2 (fd, DEVICE_TOUCHPAD, 
		     (ABSOLUTE_MODE    |
		      HIGH_REPORT_RATE |
		      PS2_NO_SLEEP     |
		      (wmode_enabled ? NO_TAPDRAG_GESTURE : TAPDRAG_GESTURE) |
		      (stick_uses_4_bytes ? STICK_4_BYTES : STICK_3_BYTES)   |
		      (wmode_enabled ? REPORT_W_ON : REPORT_W_OFF)));

}



/****************************************************************************
**
**  TRANSLATE FUNCTIONS the incoming data to an uniform report
**
****************************************************************************/

/* SERIAL PACKETS
 * STIG page 63, note 7 bit!
 *
 * byte 0 |    1   | reserved | gesture  | finger |  left   |  middle | right|  
 * byte 1 |    0   |                     x-pos 7-12                          |
 * byte 2 |    0   |                     x-pos 1-6                           |
 * byte 3 |    0   |                     y-pos 7-12                          |
 * byte 4 |    0   |                     y-pos 1-6                           |
 * byte 5 |    0   |                  z pressure 7-2                         |
 * byte 6 |    0   |  down    |   up     |y-pos 0 | x-pos 0 | z pressure 0-1 |  (new_abs set)
 * byte 7 |    0   |      reserved       |       W mode 0-3                  |  (new_abs and wmode set)
 */


static void syn_serial_translate_data (tp_class *tp,
				       unsigned char *data,
				       report_type *report) 
{
  report->buttons      = data [0] & 0x04 ? LEFT_BUTTON   : 0; 
  report->buttons     |= data [0] & 0x02 ? MIDDLE_BUTTON : 0;
  report->buttons     |= data [0] & 0x01 ? RIGHT_BUTTON  : 0;
  report->multi_buttons= 0;  
  report->gesture      = check_bits (data [0], 0x10);
  report->fingers      = check_bits (data [0], 0x08);
  report->x            = (data [1] << 7) | (data [2] << 1);
  report->y            = (data [3] << 7) | (data [4] << 1);
  report->pressure     = data [5] << 2;
  report->w            = 0;
  report->fingerwidth  = 0;

  if (tp->model[DEVICE_TOUCHPAD].new_abs){
    report->buttons  |=  data [6] & 0x20 ? UP_BUTTON   : 0;
    report->buttons  |=  data [6] & 0x40 ? DOWN_BUTTON : 0;
    report->y        |= (data [6] & 0x08) >> 3;
    report->x        |= (data [6] & 0x04) >> 2;
    report->pressure |= (data [6] & 0x03);

    if (wmode_enabled){
      report->w         = (data [7] & 0x0F);
    }
  }
}


/* PS2 PACKETS 
 *
 * Handle error packets. It may be garbage or not, as the synaptics pad keeps sending data 1
 * sec after last touch.
 */

static void syn_ps2_translate_error(unsigned char *data,
				    report_type *report)
{
  gpm_report(GPM_PR_WARN,"Unrecognized Synaptic PS/2 Touchpad packet: %02X %02X %02X %02X %02X %02X",
	     data [0],data [1],data [2],data [3],data [4],data [5]);

  if (reset_on_error_enabled) {
    /* Hack to get the fd: which_mouse is the current mouse,
       and as the synaptic code is called, it is the current mouse. */
    syn_ps2_reset(which_mouse->fd);
    syn_ps2_absolute_mode(which_mouse->fd);
  }
  
  report->buttons      = 0;
  report->multi_buttons= 0;
  report->x            = 0;
  report->y            = 0;
  report->pressure     = 0;
  report->gesture      = 0;
  report->fingers      = 0;
  report->fingerwidth  = 0;
  report->w            = 0;
  
}


/*
 * STIG page 42
 * wmode = 0, newer version. Gesture, right and left are repeated.
 *
 * byte 0 |     1   |    0   |  Finger  | Reserved |    0   | Gesture |  Right  | Left |  
 * byte 1 |         y-pos 11-8                     |         x-pos  11-8               |
 * byte 2 |                           z pressure 0-7                                   |
 * byte 3 |     1   |    1   | y-pos 12 | x-pos 12 |    0   | Gesture |  Right  | Left |
 * byte 4 |                                 x - pos 0-7                                |
 * byte 5 |                                 y - pos 0-7                                |
 *
 * STIG page 43
 * wmode = 0, old version <  3.2.
 * Second is a second gesture!?
 *
 * byte 0 |     1   |    1   |  z-pres 6-7         | Second | Gesture |  Right  | Left |  
 * byte 1 |  finger |    0   |     0    |                    x-pos  12-8               |
 * byte 2 |                           x-pos  0-7                                       |
 * byte 3 |     1   |    0   |               z-pressure 0-5                            |
 * byte 4 |Reserved |    0   |    0     |              y - pos 8-12                    |
 * byte 5 |                                 y - pos 0-7                                |
 * 
 */

/* Translate the reported data into a record for processing */
static void syn_ps2_translate_data (unsigned char *data,
				    report_type *report) 
{
  
  /* Check that this is indeed an absolute 6 byte new version packet*/
  if (((data [0] & 0xc8) == 0x80) &&              /* Check static in byte 0 */
      ((data [3] & 0xc8) == 0xc0) &&              /* Check static in byte 3 */
      ((data [0] & 0x0F) == (data [3] & 0x0F))) { /* check repeated data */
    report->buttons      = data [0] & 0x01 ? LEFT_BUTTON  : 0;
    report->buttons     |= data [0] & 0x02 ? RIGHT_BUTTON : 0;
    report->multi_buttons= 0;
    report->x            = (((data [1] & 0x0F) << 8) |
			    ((data [3] & 0x10) << 8) |
			    ((data [4])));
    report->y            = (((data [1] & 0xF0) << 4) |
			    ((data [3] & 0x20) << 7) |
			    ((data [5])));
    report->pressure     = data [2];
    report->gesture      = check_bits (data [0], 0x04);
    report->fingers      = check_bits (data [0], 0x20);
    report->fingerwidth  = 0;
    report->w            = 0;
    
  } /* Old style packet maybe */
  else if (((data [0] & 0xC0) == 0xC0) && /* Static in byte 0*/
	   ((data [1] & 0x60) == 0x00) && /* Static in byte 1*/
	   ((data [3] & 0xC0) == 0x80) && /* Static in byte 3*/
	   ((data [4] & 0x60) == 0x00)) { /* Static in byte 4*/
    report->buttons      = data [0] & 0x01 ? LEFT_BUTTON  : 0;
    report->buttons     |= data [0] & 0x02 ? RIGHT_BUTTON : 0;
    report->multi_buttons= 0;
    report->x            = (((data [1] & 0x1F) << 8) |
			    ((data [2])));
    report->y            = (((data [4] & 0x1f) << 8) |
			    ((data [5])));
    report->pressure     = (((data [0] & 0x30) << 2 ) |
			    ((data [3] & 0x3f)));
    report->gesture      = check_bits (data [0], 0x04);
    report->fingers      = check_bits (data [1], 0x80);
    report->fingerwidth  = 0;
    report->w            = 0;
    
  } else { 
    syn_ps2_translate_error(data,report);
  }
}


/* STIG page 42
 * wmode = 1, 
 *
 * byte 0 |     1   |    0   |  W 2-3              |    0   | W 1     |  Right  | Left |  
 * byte 1 |         y-pos 11-8                     |         x-pos  11-8               |
 * byte 2 |                           z pressure 0-7                                   |
 * byte 3 |     1   |    1   | y-pos 12 | x-pos 12 |    0   | W 0     |  R/D    | L/U  |
 * byte 4 |                                 x - pos 0-7                                |
 * byte 5 |                                 y - pos 0-7                                |
 *
 */

static void syn_ps2_translate_wmode_data (unsigned char *data,
					  report_type *report) 
{
  /* Check that it is an absolute packet */
  if (((data[0] & 0xc8) == 0x80) && ((data[3] & 0xc8) == 0xc0)) {

    report->buttons      =  data[0]            & 0x01 ? LEFT_BUTTON   : 0;
    report->buttons     |= (data[0] ^ data[3]) & 0x01 ? MIDDLE_BUTTON : 0;
    report->buttons     |=  data[0]            & 0x02 ? RIGHT_BUTTON  : 0;
    report->buttons     |= (data[0] ^ data[3]) & 0x02 ? FOURTH_BUTTON : 0;
    report->x            = (((data[1] & 0x0F) << 8) |
			    ((data[3] & 0x10) << 8) |
			    ((data[4])));
    report->y            = (((data[1] & 0xF0) << 4) |
			    ((data[3] & 0x20) << 7) |
			    ((data[5])));
    report->pressure     = data[2];
    report->gesture      = 0;
    report->fingers      = 0;
    report->fingerwidth  = 0;
    report->w = (((data[3] & 0x04) >> 2) |
		 ((data[0] & 0x04) >> 1) |
		 ((data[0] & 0x30) >> 2));
    
    report->multi_buttons  = 0;
    if (multi_buttons_no && (report->buttons & FOURTH_BUTTON)) {
      report->buttons &= ~FOURTH_BUTTON;
      switch (multi_buttons_no & ~0x01) {
      case 8: report->multi_buttons |= (data[4] & 0x08) << 3 | (data[5] & 0x08) << 4;
      case 6: report->multi_buttons |= (data[4] & 0x04) << 2 | (data[5] & 0x04) << 3;
      case 4: report->multi_buttons |= (data[4] & 0x02) << 1 | (data[5] & 0x02) << 2;
      case 2: report->multi_buttons |= (data[4] & 0x01) << 0 | (data[5] & 0x01) << 1;
      }
    } 
  } else { 
    syn_ps2_translate_error(data,report);
  }
}


/****************************************************************************
**
** INTERFACE ROUTINES
**
****************************************************************************/

/*
** syn_process_serial_data
**
** Process the touchpad 6 byte report.
*/
void syn_process_serial_data (Gpm_Event *state,
			      unsigned char *data) 
{
#ifdef DEBUG_RAW
  gpm_report(GPM_PR_DEBUG,"Data %02x %02x %02x %02x %02x %02x",data[0],data[1],data[2],data[3],data[4],data[5]); 
#endif

  /* initialize the state */
  state->buttons = 0;
  state->dx      = 0;
  state->dy      = 0;

  syn_serial_translate_data (TP, data, &cur_report);
  if (wmode_enabled){
    syn_process_wmode_report(TP, &cur_report);
  }    
  if (tp_find_fingers(&cur_report,state)) return;
  if (wmode_enabled){
    tp_find_gestures(&cur_report);    
  }    

  tp_process_report (state, &cur_report);
}


/*
** syn_serial_reset
**
** Reset the touchpad to relative mode.  This cannot be called directly as a
** command should be sent in 1200 baud, not 9600.
*/

void syn_serial_reset(int fd)
{
  gpm_report (GPM_PR_INFO,"Reseting Synaptic Serial Touchpad.");

  syn_serial_set_mode (fd, (RELATIVE_MODE    |
			    HIGH_REPORT_RATE |
			    USE_9600_BAUD    |
			    NORMAL_REPORT    |
			    REPORT_W_OFF));

}

/*
** syn_serial_init
** 
** Initialize the synaptics touchpad.  Read model and identification.
** Determine the size of the touchpad in "pixels".  Select 6/7/8 byte packets,
** select 9600 baud, and select high packet rate.
*/
int syn_serial_init (int fd) 
{
  int return_packetlength;

  gpm_report(GPM_PR_DEBUG,"Initializing Synaptics Serial TouchPad");

  /* Succes or errorexit outs*/
  TP = tp_allocate_tp_class(fd);

  syn_serial_read_ident(TP);
  syn_serial_read_model_id(TP);
  syn_serial_read_cap(TP);

  syn_set_sensor_info(TP, DEVICE_TOUCHPAD);

  